/*
 * This file is part of Splice Machine.
 * Splice Machine is free software: you can redistribute it and/or modify it under the terms of the
 * GNU Affero General Public License as published by the Free Software Foundation, either
 * version 3, or (at your option) any later version.
 * Splice Machine is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
 * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU Affero General Public License for more details.
 * You should have received a copy of the GNU Affero General Public License along with Splice Machine.
 * If not, see <http://www.gnu.org/licenses/>.
 *
 * Some parts of this source code are based on Apache Derby, and the following notices apply to
 * Apache Derby:
 *
 * Apache Derby is a subproject of the Apache DB project, and is licensed under
 * the Apache License, Version 2.0 (the "License"); you may not use these files
 * 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.
 *
 * Splice Machine, Inc. has modified the Apache Derby code in this file.
 *
 * All such Splice Machine modifications are Copyright 2012 - 2020 Splice Machine, Inc.,
 * and are licensed to you under the GNU Affero General Public License.
 */

package com.splicemachine.dbTesting.functionTests.tests.lang;

import java.sql.SQLException;
import java.sql.SQLWarning;
import java.sql.Connection;
import java.sql.Statement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.DatabaseMetaData;
import java.sql.DriverManager;
import java.util.ArrayList;
import junit.framework.Test;
import junit.framework.TestSuite;
import com.splicemachine.dbTesting.junit.BaseJDBCTestCase;
import com.splicemachine.dbTesting.junit.JDBC;
import com.splicemachine.dbTesting.junit.DatabasePropertyTestSetup;
import com.splicemachine.dbTesting.junit.TestConfiguration;
import com.splicemachine.dbTesting.junit.CleanDatabaseTestSetup;

/**
 * This tests that SQL roles actually confer the correct privileges, that is,
 * when privileges are granted to one or more roles and those roles are granted
 * to other roles, to one or more users or to PUBLIC, sessions can make use of
 * them correctly.
 */
public class RolesConferredPrivilegesTest extends BaseJDBCTestCase
{
    private final static String pwSuffix = "pwSuffix";

    /* SQL states */
    private final static String NOEXECUTEPERMISSION  = "42504";
    private final static String NOTABLEPERMISSION    = "42500";
    private final static String NOCOLUMNPERMISSION   = "42502";
    private final static String TABLENOTFOUND        = "42X05";
    private final static String OBJECTNOTFOUND       = "42X94";
    private final static String FKVIOLATION          = "23503";
    private final static String CHECKCONSTRAINTVIOLATED = "23513";
    private final static String ALREADYCLOSED        = "XJ012";
    private final static String CONSTRAINTDROPPED    = "01500";
    private final static String VIEWDROPPED          = "01501";
    private final static String TRIGGERDROPPED       = "01502";
    private final static String UNRELIABLE           = "42Y39";

    private final static String[] users = {"test_dbo", "DonaldDuck", "MickeyMouse"};

    /**
     * Create a new instance of RolesConferredPrivilegesTest.
     *
     * @param name Fixture name
     */
    public RolesConferredPrivilegesTest(String name)
    {
        super(name);
    }


    /**
     * Construct top level suite in this JUnit test
     *
     * @return A suite containing embedded and client suites.
     */
    public static Test suite()
    {
        TestSuite suite = new TestSuite("RolesConferredPrivilegesTest");

        suite.addTest(makeSuite());

        // suite.addTest(
        //     TestConfiguration.clientServerDecorator(makeSuite()));

        return suite;
    }

    /**
     * Construct suite of tests
     *
     * @return A suite containing the test cases.
     */
    private static Test makeSuite()
    {
        /* Tests running with sql authorization set.  First decorate
         * with clean database, then with authentication +
         * sqlAuthorization.
         */
        Test clean = new CleanDatabaseTestSetup(
            new TestSuite(RolesConferredPrivilegesTest.class)) {
                protected void decorateSQL(Statement s)
                        throws SQLException {
                    /*
                     *          a1            a2         a3
                     *         / | \           |          |
                     *        /  b  +--------> c          d
                     *       j   |              \        /
                     *           e---+           \      /
                     *            \   \           \    /
                     *             \   \---------+ \  /
                     *              \             \F f
                     *               \             /
                     *                \           /
                     *                 \         /
                     *                  \       /
                     *                   \     /
                     *                    \   /
                     *                      h
                     */
                    s.execute("create role a1");
                    s.execute("create role j");
                    s.execute("create role b");
                    s.execute("create role e");
                    s.execute("create role h");
                    s.execute("create role a2");
                    s.execute("create role c");
                    s.execute("create role f");
                    s.execute("create role a3");
                    s.execute("create role d");

                    s.execute("grant a1 to j");
                    s.execute("grant a1 TO b");
                    s.execute("grant b TO e");
                    s.execute("grant e TO h");
                    s.execute("grant a1 TO c");
                    s.execute("grant e TO f");
                    s.execute("grant a2 TO c");
                    s.execute("grant c TO f");
                    s.execute("grant f TO h");
                    s.execute("grant a3 TO d");
                    s.execute("grant d TO f");

                    s.execute("create schema s1");
                    s.execute
                        ("create function s1.f1( ) returns int " +
                         "language java parameter style java external name " +
                         "'com.splicemachine.dbTesting.functionTests.tests.lang." +
                         "RolesConferredPrivilegesTest.s1f1' " +
                         "no sql called on null input");
                    s.execute
                        ("create function s1.f2( ) returns int " +
                         "language java parameter style java external name " +
                         "'com.splicemachine.dbTesting.functionTests.tests.lang." +
                         "RolesConferredPrivilegesTest.s1f1' " +
                         "no sql called on null input");
                    s.execute
                        ("create table s1.t1(" +
                         "c1 int unique, c2 int unique, c3 int unique, " +
                         "primary key (c1,c2,c3))");
                    // We made columns all unique so we can test references
                    // privilege for all columns.
                    s.execute(
                        "create procedure s1.calledNested()" +
                        "  language java parameter style java" +
                        "  external name " +
                        "'com.splicemachine.dbTesting.functionTests.tests.lang." +
                        "RolesConferredPrivilegesTest.calledNested' " +
                        "  modifies sql data");
                    s.execute
                        ("create function s1.getCurrentRole() " +
                         "returns varchar(30)" +
                         "language java parameter style java external name " +
                         "'com.splicemachine.dbTesting.functionTests.tests.lang." +
                         "RolesConferredPrivilegesTest.getCurrentRole' " +
                         " reads sql data");
                }
            };

        return
            TestConfiguration.sqlAuthorizationDecorator(
                DatabasePropertyTestSetup.singleProperty(
                    DatabasePropertyTestSetup.builtinAuthentication(
                        clean, users, pwSuffix),
                    // Increase default statementCacheSize since we compile a
                    // lot:
                    "derby.language.statementCacheSize", "1000"));
    }

    private final static int GRANT = 0;
    private final static int REVOKE = 1;

    // Action type for assert methods.
    private final static int NOPRIV = 0;
    private final static int VIAUSER = 1;
    private final static int VIAROLE = 2;

    // The "guts" of GRANT/REVOKE privilege strings. Prepend GRANT/REVOKE and
    // append TO/FROM authorizationId [RESTRICT] as the case may be.
    private final static String g_r    = "references on s1.t1 ";
    private final static String g_r_c1 = "references (c1) on s1.t1 ";
    private final static String g_r_c2 = "references (c2) on s1.t1 ";
    private final static String g_r_c3 = "references (c3) on s1.t1 ";
    private final static String g_u    = "update on s1.t1 ";
    private final static String g_u_c1 = "update (c1) on s1.t1 ";
    private final static String g_u_c2 = "update (c2) on s1.t1 ";
    private final static String g_u_c3 = "update (c3) on s1.t1 ";
    private final static String g_u_c1_c2_c3 = "update (c1,c2,c3) on s1.t1 ";
    private final static String g_i    = "insert on s1.t1 ";
    private final static String g_s    = "select on s1.t1 ";
    private final static String g_s_c1 = "select (c1) on s1.t1 ";
    private final static String g_s_c2 = "select (c2) on s1.t1 ";
    private final static String g_s_c3 = "select (c3) on s1.t1 ";
    private final static String g_d    = "delete on s1.t1 ";
    private final static String g_t    = "trigger on s1.t1 ";
    private final static String g_e    = "execute on function s1.f1 ";
    private final static String g_e_f2 = "execute on function s1.f2 ";

    // Collections of privileges
    private final static String[] g_all =
        new String[] {g_r, g_r_c1, g_r_c2, g_r_c3,
                      g_u, g_u_c1, g_u_c2, g_u_c3,
                      g_i,
                      g_s, g_s_c1, g_s_c2, g_s_c3,
                      g_d,
                      g_t,
                      g_e};
    // column level (for privilege types applicable)
    private final static String[] g_all_col =
        new String[] {g_r_c1, g_r_c2, g_r_c3,
                      g_u_c1, g_u_c2, g_u_c3,
                      g_i,
                      g_s_c1, g_s_c2, g_s_c3,
                      g_d,
                      g_t,
                      g_e};
    // table level
    private final static String[] g_all_tab =
        new String[] {g_r, g_u, g_i, g_s, g_d, g_t, g_e};

    private final static String[][]grantRevokes =
        new String[][] {g_all, g_all_col, g_all_tab};

    /**
     * Basic test that checks that privileges granted to a role are applicable
     * when the user sets the current role to that role (or to a role that
     * inherits that role).
     *
     * @throws SQLException
     */
    public void atestConferredPrivileges() throws SQLException
    {
        Connection dboConn = getConnection();
        Statement s = dboConn.createStatement();

        // try with role granted to both a user and to public
        String[] grantees = new String[] {"DonaldDuck", "public"};

        Connection c = openUserConnection("DonaldDuck");

        for (int gNo = 0; gNo < grantees.length; gNo++ ) {
            /*
             * Grant a role to a session's user and test that privileges apply
             * when granted to any applicable role.
             */
            s.executeUpdate("grant h to " + grantees[gNo]);

            String[] applicableFor_h =
                new String[] {"h", "a1", "a2", "a3", "b", "e", "c", "f", "d"};
            String[] notApplicableFor_h =
                new String[] {"j"};
            setRole(c, "h");

            // Test that all privileges apply when granted to the current role
            // or to an inherited role of the current role, cf graph above.
            for (int i=0; i < applicableFor_h.length; i++) {
                assertAllforRole(VIAROLE, c, applicableFor_h[i]);
            }

            // Test that no privileges apply when granted to a role NOT
            // inherited by the current role.
            for (int i=0; i < notApplicableFor_h.length; i++) {
                assertAllforRole(NOPRIV, c, notApplicableFor_h[i]);
            }

            /*
             * Test that no privileges apply when we set role to none.
             */
            setRole(c, "none");

            for (int i=0; i < applicableFor_h.length; i++) {
                assertAllforRole(NOPRIV, c, applicableFor_h[i]);
            }

            /*
             * Test that when one link in the graph "cycle" is broken,
             * privileges still apply.
             */
            s.executeUpdate("grant f to " + grantees[gNo]);
            setRole(c, "f");
            assertAllforRole(VIAROLE, c, "a1");
            // one arc gone
            s.executeUpdate("revoke a1 from c");
            assertAllforRole(VIAROLE, c, "a1");
            // both arcs gone
            s.executeUpdate("revoke a1 from b");
            assertAllforRole(NOPRIV, c, "a1");
            // resurrect other arc
            s.executeUpdate("grant a1 to b");
            assertAllforRole(VIAROLE, c, "a1");
            // restore things
            s.executeUpdate("grant a1 to c");
            s.executeUpdate("revoke f from " + grantees[gNo]);

            /*
             * Revoke the role from the current session's user and verify that
             * privileges no longer apply when granted to any heretofore
             * applicable role.
             */
            setRole(c, "h");

            s.executeUpdate("revoke h from " + grantees[gNo]);

            for (int i=0; i < applicableFor_h.length; i++) {
                assertAllforRole(NOPRIV, c, applicableFor_h[i]);
            }

            /*
             * Test when role is dropped when still a current role
             */
            s.executeUpdate("grant h to " + grantees[gNo]);
            setRole(c, "h");

            // Test that all privileges apply when granted to the current role
            // or to an inherited role of the current role, cf graph above.
            for (int i=0; i < applicableFor_h.length; i++) {
                assertAllforRole(VIAROLE, c, applicableFor_h[i]);
            }

            s.executeUpdate("drop role h");

            for (int i=0; i < applicableFor_h.length; i++) {
                assertAllforRole(NOPRIV, c, applicableFor_h[i]);
            }

            // restore the dropped role
            s.executeUpdate("create role h");
            s.executeUpdate("grant e to h");
            s.executeUpdate("grant f to h");
        }

        c.close();
        s.close();
        dboConn.close();
    }


    /**
     * When a view, a trigger or a constraint requires a privilege by way of
     * the current role (or by way of a role inherited by the current role) at
     * creation time (SELECT, TRIGGER or REFERENCES privilege respectively), a
     * dependency is also registered against the current role. Whenever that
     * role (it need no longer be current) - or indeed one of its inherited
     * roles - is revoked (from the current user or from a role in the closure
     * of the original current role) or dropped, the dependent view, trigger or
     * constraint is potentially invalidated (the single dependency is against
     * the original current role, not against the potentially n-ary set of
     * roles in the closure of the current role used to find the required
     * privileges). Due to DERBY-1632, currently the objects are dropped
     * instead of being potentially revalidated.
     *
     * These tests check that invalidation actually happens and leads to a
     * dropping of the dependent view (there are also no revalidation
     * possibilities in play here, so even when DERBY-1632 is fixed these tests
     * should work).
     */
    public void testViewInvalidation() throws SQLException {
        Connection dboConn = getConnection();
        Statement s = dboConn.createStatement();

        Connection c = openUserConnection("DonaldDuck");
        Statement cStmt = c.createStatement();
        SQLWarning w;
        /*
        * 3-dimensional search space:
        *
        * Which role we grant the role to (direct to a role or to a role it
        * inherits)
        *    X
        * Whether the role is granted directly to the session user or to PUBLIC.
        *    X
        * Whether we grant the entire underlying table or just the column
        * needed.
        */
        String[] grantToThisRole = new String[] {"a2", "h"};
        String[] roleGrantees = new String[] {"DonaldDuck", "public"};
        String[] tabAndColSelectsPerms = new String[] {g_s, g_s_c1};
        String createViewString = "create view v as select c1 from s1.t1";

        for (int r = 0; r < grantToThisRole.length; r++) {
            for (int gNo = 0; gNo < roleGrantees.length; gNo++ ) {
                for (int i = 0; i < tabAndColSelectsPerms.length; i++) {
                    /*
                     * Create a view on the basis of a select privilege via a
                     * role.
                     */
                    s.executeUpdate("grant h to " + roleGrantees[gNo]);

                    doGrantRevoke(GRANT, "test_dbo", tabAndColSelectsPerms[i],
                                  grantToThisRole[r]);

                    setRole(c, "h");
                    cStmt.executeUpdate(createViewString);

                    assertViewExists(true, c, "v");

                    /*
                     * Setting another role does not affect the view once
                     * defined.
                     */
                    setRole(c, "none");
                    assertViewExists(true, c, "v");

                    /*
                     * Remove privileges from role, and the view should be
                     * gone.
                     */
                     doGrantRevoke(REVOKE, "test_dbo", tabAndColSelectsPerms[i],
                                   grantToThisRole[r], VIEWDROPPED);
                    assertViewExists(false, c, "v");

                    /*
                     * Revoking the role should also invalidate view
                     */
                    doGrantRevoke(GRANT, "test_dbo", tabAndColSelectsPerms[i],
                                  grantToThisRole[r]);

                    setRole(c, "h");
                    cStmt.executeUpdate(createViewString);
                    assertViewExists(true, c, "v");

                    s.executeUpdate("revoke h from " + roleGrantees[gNo]);
                    w = s.getWarnings();
                    assertSQLState(VIEWDROPPED, w);
                    assertViewExists(false, c, "v");

                    /*
                     * Check that user privilege and/or PUBLIC privilege is
                     * preferred over role privilege if available. This is not
                     * standard SQL, but useful behavior IMHO as long as Derby
                     * can't revalidate via another path (DERBY-1632) - lest a
                     * role revoke or drop causes an invalidation when user has
                     * discretionary privilege. Cf. also comment on priority of
                     * user vs public in DERBY-1611.
                     */
                    String[] directGrantee = roleGrantees;

                    for (int u = 0; u < directGrantee.length; u++) {
                        s.executeUpdate("grant h to " + roleGrantees[gNo]);
                        doGrantRevoke(GRANT, "test_dbo",
                                      tabAndColSelectsPerms[i],
                                      directGrantee[u]);

                        setRole(c, "h");

                        // Now we have select privilege two ways, via role and
                        // via user.
                        cStmt.executeUpdate(createViewString);

                        // Now revoke role priv and see that view is still
                        // unaffected.
                        s.executeUpdate("revoke h from " + roleGrantees[gNo]);
                        assertViewExists(true, c, "v");

                        // Take away user privilege, too.
                        doGrantRevoke(REVOKE, "test_dbo",
                                      tabAndColSelectsPerms[i],
                                      directGrantee[u],
                                      VIEWDROPPED);
                        assertViewExists(false, c, "v");
                    }

                    // clean up
                    doGrantRevoke(REVOKE, "test_dbo",tabAndColSelectsPerms[i],
                                  grantToThisRole[r]);
                }
            }
        }

        /*
         * Dropping a role should also invalidate a dependent view.
         *
         * (We do this test outside the loop above for simplicity of
         * reestablish role graph after the drop..)
         */

        // drop the current role
        doGrantRevoke(GRANT, "test_dbo", g_s, "h");
        s.executeUpdate("grant h to DonaldDuck");

        setRole(c, "h");
        cStmt.executeUpdate(createViewString);
        assertViewExists(true, c, "v");

        s.executeUpdate("drop role h");

        w = s.getWarnings();
        assertSQLState(VIEWDROPPED, w);
        assertViewExists(false, c, "v");

        doGrantRevoke(REVOKE, "test_dbo", g_s, "h");

        // re-establish role graph
        s.executeUpdate("create role h");
        s.executeUpdate("grant e to h");
        s.executeUpdate("grant f to h");

        // drop an inherited role needed
        doGrantRevoke(GRANT, "test_dbo", g_s, "a3");
        s.executeUpdate("grant h to DonaldDuck");

        setRole(c, "h");
        cStmt.executeUpdate(createViewString);
        assertViewExists(true, c, "v");

        s.executeUpdate("drop role a3");

        w = s.getWarnings();
        assertSQLState(VIEWDROPPED, w);
        assertViewExists(false, c, "v");

        doGrantRevoke(REVOKE, "test_dbo", g_s, "h");

        // re-establish role graph
        s.executeUpdate("create role a3");
        s.executeUpdate("grant a3 to d");

        cStmt.close();
        c.close();
        s.close();
        dboConn.close();
    }


    /**
     * @see #testViewInvalidation
     */
    public void testTriggerInvalidation() throws SQLException {
        Connection dboConn = getConnection();
        Statement s = dboConn.createStatement();

        Connection c = openUserConnection("DonaldDuck");
        Statement cStmt = c.createStatement();
        SQLWarning w;

        /*
        * 2-dimensional search space:
        *
        * Which role we grant the role to (direct to a role or to a role it
        * inherits)
        *    X
        * Whether the role is granted directly to the session user or to PUBLIC.
        */
        String[] grantToThisRole = new String[] {"a2", "h"};
        String[] roleGrantees = new String[] {"DonaldDuck", "public"};
        String createTriggerString =
            "create trigger t after insert on s1.t1 values 1";

        for (int r = 0; r < grantToThisRole.length; r++) {
            for (int gNo = 0; gNo < roleGrantees.length; gNo++ ) {
                /*
                 * Create a trigger on the basis of a trigger privilege via a
                 * role.
                 */
                s.executeUpdate("grant h to " + roleGrantees[gNo]);

                doGrantRevoke(GRANT, "test_dbo", g_t, grantToThisRole[r]);

                setRole(c, "h");
                cStmt.executeUpdate(createTriggerString);

                assertTriggerExists(true, c, "t");
                cStmt.executeUpdate(createTriggerString);
                /*
                 * Setting another role does not affect the trigger once
                 * defined.
                 */
                setRole(c, "none");
                assertTriggerExists(true, c, "t");

                setRole(c, "h");
                cStmt.executeUpdate(createTriggerString);

                // Remove privileges from role, and the trigger should be
                // gone.
                doGrantRevoke(REVOKE, "test_dbo", g_t, grantToThisRole[r],
                              TRIGGERDROPPED);

                assertTriggerExists(false, c, "t");

                /*
                 * Revoking the role should also invalidate trigger
                 */
                doGrantRevoke(GRANT, "test_dbo", g_t, grantToThisRole[r]);

                setRole(c, "h");
                cStmt.executeUpdate(createTriggerString);
                assertTriggerExists(true, c, "t");
                cStmt.executeUpdate(createTriggerString);

                s.executeUpdate("revoke h from " + roleGrantees[gNo]);
                w = s.getWarnings();
                assertSQLState(TRIGGERDROPPED, w);
                assertTriggerExists(false, c, "t");

                /*
                 * Check that user privilege and/or PUBLIC privilege is
                 * preferred over role privilege if available. This is not
                 * standard SQL, but useful behavior IMHO as long as Derby
                 * can't revalidate via another path (DERBY-1632) - lest a
                 * role revoke or drop causes an invalidation when user has
                 * discretionary privilege. Cf. also comment on priority of
                 * user vs public in DERBY-1611.
                 */
                String[] directGrantee = roleGrantees;

                for (int u = 0; u < directGrantee.length; u++) {
                    s.executeUpdate("grant h to " + roleGrantees[gNo]);
                    doGrantRevoke(GRANT, "test_dbo", g_t, directGrantee[u]);

                    setRole(c, "h");

                    // Now we have trigger privilege two ways,a via role and
                    // via user.
                    cStmt.executeUpdate(createTriggerString);

                    // Now revoke role priv and see that trigger is still
                    // unaffected.
                    s.executeUpdate("revoke h from " + roleGrantees[gNo]);
                    assertTriggerExists(true, c, "t");
                    cStmt.executeUpdate(createTriggerString);

                    // take away user privilege, too
                    doGrantRevoke(REVOKE, "test_dbo",g_t,directGrantee[u],
                                  TRIGGERDROPPED);
                    assertTriggerExists(false, c, "t");
                }

                // clean up
                doGrantRevoke(REVOKE, "test_dbo", g_t, grantToThisRole[r]);
            }
        }

        /*
         * Dropping a role should also invalidate a dependent trigger.
         *
         * (We do this test outside the loop above for simplicity of
         * reestablish role graph after the drop..)
         */
        doGrantRevoke(GRANT, "test_dbo", g_t, "h");
        s.executeUpdate("grant h to DonaldDuck");

        setRole(c, "h");
        cStmt.executeUpdate(createTriggerString);
        assertTriggerExists(true, c, "t");
        cStmt.executeUpdate(createTriggerString);

        s.executeUpdate("drop role h");
        w = s.getWarnings();
        assertSQLState(TRIGGERDROPPED, w);
        assertTriggerExists(false, c, "t");

        doGrantRevoke(REVOKE, "test_dbo", g_t, "h");

        // re-establish role graph
        s.executeUpdate("create role h");
        s.executeUpdate("grant e to h");
        s.executeUpdate("grant f to h");

        /*
         * Dropping an EXECUTE privilege used in a trigger body will not drop
         * the trigger if the EXECUTE privilege is revoked from a user
         * directly, since this currently requires the RESTRICT
         * keyword. However, revoking a role does not carry the RESTRICT
         * keyword, so any execution privilege conferred through a role is
         * revoked, too, and any dependent object, for example a trigger in
         * example below, will be dropped.
         */
        doGrantRevoke(GRANT, "test_dbo", g_t, "h");
        doGrantRevoke(GRANT, "test_dbo", g_e, "h");
        s.executeUpdate("grant h to DonaldDuck");
        setRole(c, "h");

        cStmt.executeUpdate
            ("create trigger t after insert on s1.t1 values s1.f1()");
        assertTriggerExists(true, c, "t");

        cStmt.executeUpdate
            ("create trigger t after insert on s1.t1 values s1.f1()");

        s.executeUpdate("revoke h from DonaldDuck");
        w = s.getWarnings();
        assertSQLState(TRIGGERDROPPED, w);
        assertTriggerExists(false, c, "t");

        doGrantRevoke(REVOKE, "test_dbo", g_t, "h");
        doGrantRevoke(REVOKE, "test_dbo", g_e, "h");

        /*
         * Check that dependency on role and subsequent invalidation happens
         * for a mix of column SELECT privileges granted to user, public and
         * role (due to tricky logic in this implementation,
         * cf. DDLConstantAction#storeViewTriggerDependenciesOnPrivileges
         */

        // SELECT privileges to {public, role} x
        // TRIGGER privilege to {user, role}
        String triggerPrivGrantees[] = new String[] {"h", "DonaldDuck"};
        for (int i=0; i < triggerPrivGrantees.length; i++) {
            s.executeUpdate("grant h to DonaldDuck");
            setRole(c, "h");
            doGrantRevoke(GRANT, "test_dbo", g_t, triggerPrivGrantees[i]);
            doGrantRevoke(GRANT, "test_dbo", g_s_c1, "public");
            doGrantRevoke(GRANT, "test_dbo", g_s_c2, "h");
            cStmt.executeUpdate
                ("create trigger t after insert on s1.t1 " +
                 "select c1,c2 from s1.t1");
            s.executeUpdate("revoke h from DonaldDuck");
            w = s.getWarnings();
            assertSQLState(TRIGGERDROPPED, w);
            assertTriggerExists(false, c, "t");
            doGrantRevoke(REVOKE, "test_dbo", g_t, triggerPrivGrantees[i]);
            doGrantRevoke(REVOKE, "test_dbo", g_s_c1, "public");
            doGrantRevoke(REVOKE, "test_dbo", g_s_c2, "h");
        }

        // SELECT privileges to {user, role} x
        // TRIGGER privilege to {user, role}
        for (int i=0; i < triggerPrivGrantees.length; i++) {
            s.executeUpdate("grant h to DonaldDuck");
            setRole(c, "h");
            doGrantRevoke(GRANT, "test_dbo", g_t, triggerPrivGrantees[i]);
            doGrantRevoke(GRANT, "test_dbo", g_s_c1, "DonaldDuck");
            doGrantRevoke(GRANT, "test_dbo", g_s_c2, "h");
            cStmt.executeUpdate
                ("create trigger t after insert on s1.t1 " +
                 "select c1,c2 from s1.t1");
            s.executeUpdate("revoke h from DonaldDuck");
            w = s.getWarnings();
            assertSQLState(TRIGGERDROPPED, w);
            assertTriggerExists(false, c, "t");
            doGrantRevoke(REVOKE, "test_dbo", g_t,  triggerPrivGrantees[i]);
            doGrantRevoke(REVOKE, "test_dbo", g_s_c1, "DonaldDuck");
            doGrantRevoke(REVOKE, "test_dbo", g_s_c2, "h");
        }

        // SELECT privileges to {user, public, role} x
        // TRIGGER privilege to {user, role}
        for (int i=0; i < triggerPrivGrantees.length; i++) {
            s.executeUpdate("grant h to DonaldDuck");
            setRole(c, "h");
            doGrantRevoke(GRANT, "test_dbo", g_t, triggerPrivGrantees[i]);
            doGrantRevoke(GRANT, "test_dbo", g_s_c1, "DonaldDuck");
            doGrantRevoke(GRANT, "test_dbo", g_s_c2, "public");
            doGrantRevoke(GRANT, "test_dbo", g_s_c3, "h");
            cStmt.executeUpdate
                ("create trigger t after insert on s1.t1 " +
                 "select c1,c2,c3 from s1.t1");
            s.executeUpdate("revoke h from DonaldDuck");
            w = s.getWarnings();
            assertSQLState(TRIGGERDROPPED, w);
            assertTriggerExists(false, c, "t");
            doGrantRevoke(REVOKE, "test_dbo", g_t, triggerPrivGrantees[i]);
            doGrantRevoke(REVOKE, "test_dbo", g_s_c1, "DonaldDuck");
            doGrantRevoke(REVOKE, "test_dbo", g_s_c2, "public");
            doGrantRevoke(REVOKE, "test_dbo", g_s_c3, "h");
        }

        cStmt.close();
        c.close();
        s.close();
        dboConn.close();
    }


    /**
     * @see #testViewInvalidation
     */
    public void testConstraintInvalidation() throws SQLException {
        Connection dboConn = getConnection();
        Statement s = dboConn.createStatement();

        Connection c = openUserConnection("DonaldDuck");
        Statement cStmt = c.createStatement();
        SQLWarning w;
        /*
        * 3-dimensional search space:
        *
        * Which role we grant the role to (direct to a role or to a role it
        * inherits)
        *    X
        * Whether the role is granted directly to the session user or to PUBLIC.
        *    X
        * Whether we grant the entire underlying table or just the column
        * needed.
        */
        String[] grantToThisRole = new String[] {"a2", "h"};
        String[] roleGrantees = new String[] {"DonaldDuck", "public"};
        String[][] tabAndColReferencesPerms =
            new String[][] {{g_r}, {g_r_c1, g_r_c2, g_r_c3}};

        String createTableString =
            "create table t (i int not null, j int, k int)";
        String dropTableString = "drop table t";
        String addConstraintString =
            "alter table t add constraint fk " +
            "foreign key(i,j,k) references s1.t1";

        cStmt.executeUpdate(createTableString);

        for (int r = 0; r < grantToThisRole.length; r++) {
            for (int gNo = 0; gNo < roleGrantees.length; gNo++ ) {
                for (int i = 0; i < tabAndColReferencesPerms.length; i++) {
                    /*
                     * Create a foreign key constraint on the basis of a
                     * references privilege via a role.
                     */
                    s.executeUpdate("grant h to " + roleGrantees[gNo]);

                    doGrantRevoke(GRANT, "test_dbo",
                                  tabAndColReferencesPerms[i],
                                  grantToThisRole[r]);

                    setRole(c, "h");
                    cStmt.executeUpdate(addConstraintString);

                    assertFkConstraintExists(true, c, "t");

                    /*
                     * Setting another role does not affect the constraint once
                     * defined.
                     */
                    setRole(c, "none");
                    assertFkConstraintExists(true, c, "t");

                    // Remove privileges from role, and the constraint should be
                    // gone.
                    doGrantRevoke
                        (REVOKE, "test_dbo",
                         tabAndColReferencesPerms[i],
                         grantToThisRole[r],
                         new String[]{CONSTRAINTDROPPED, null, null});

                    assertFkConstraintExists(false, c, "t");

                    /*
                     * Revoking the role should also invalidate constraint
                     */
                    doGrantRevoke(GRANT, "test_dbo",
                                  tabAndColReferencesPerms[i],
                                  grantToThisRole[r]);

                    setRole(c, "h");
                    cStmt.executeUpdate(addConstraintString);
                    assertFkConstraintExists(true, c, "t");

                    s.executeUpdate("revoke h from " + roleGrantees[gNo]);
                    assertFkConstraintExists(false, c, "t");

                    /*
                     * Check that user privilege and/or PUBLIC privilege is
                     * preferred over role privilege if available. This is not
                     * standard SQL, but useful behavior IMHO as long as Derby
                     * can't revalidate via another path (DERBY-1632) - lest a
                     * role revoke or drop causes an invalidation when user has
                     * discretionary privilege. Cf. also comment on priority of
                     * user vs public in DERBY-1611.
                     */
                    String[] directGrantee = roleGrantees;

                    for (int u = 0; u < directGrantee.length; u++) {
                        s.executeUpdate("grant h to " + roleGrantees[gNo]);
                        doGrantRevoke(GRANT, "test_dbo",
                                      tabAndColReferencesPerms[i],
                                      directGrantee[u]);

                        setRole(c, "h");

                        // Now we have references privilege two ways, via role
                        // and via user.
                        cStmt.executeUpdate(addConstraintString);

                        // Now revoke role priv and see that constraints is
                        // still unaffected.
                        s.executeUpdate("revoke h from " + roleGrantees[gNo]);
                        assertFkConstraintExists(true, c, "t");

                        // take away user privilege, too
                        doGrantRevoke
                            (REVOKE, "test_dbo",
                             tabAndColReferencesPerms[i],
                             directGrantee[u],
                             new String[]{CONSTRAINTDROPPED, null, null});
                        assertFkConstraintExists(false, c, "t");
                    }

                    // clean up
                    doGrantRevoke
                        (REVOKE, "test_dbo",
                         tabAndColReferencesPerms[i],
                         grantToThisRole[r]);
                }
            }
        }

        /*
         * Dropping a role should also invalidate a dependent constraint.
         *
         * (We do this test outside the loop above for simplicity of
         * reestablish role graph after the drop..)
         */
        doGrantRevoke(GRANT, "test_dbo", g_r, "h");
        s.executeUpdate("grant h to DonaldDuck");

        setRole(c, "h");
        cStmt.executeUpdate(addConstraintString);
        assertFkConstraintExists(true, c, "t");

        s.executeUpdate("drop role h");
        w = s.getWarnings();
        assertSQLState(CONSTRAINTDROPPED, w);
        assertFkConstraintExists(false, c, "t");


        doGrantRevoke(REVOKE, "test_dbo", g_s, "h");


        // re-establish role graph
        s.executeUpdate("create role h");
        s.executeUpdate("grant e to h");
        s.executeUpdate("grant f to h");

        /*
         * For FOREIGN KEY constraint, check that dependency on role and
         * subesquent invalidation happens for a mix of column privileges
         * granted to user, public and role (due to tricky logic in this
         * implementation,
         * cf. DDLConstantAction#storeConstraintDependenciesOnPrivileges
         */
        // {role, role}
        s.executeUpdate("grant h to DonaldDuck");
        setRole(c, "h");
        doGrantRevoke(GRANT, "test_dbo",
                      new String[] {g_r_c1, g_r_c2, g_r_c3}, "h");
        cStmt.executeUpdate
            ("alter table t add constraint fk foreign key(i,j,k) " +
             "references s1.t1");

        s.executeUpdate("revoke h from DonaldDuck");
        w = s.getWarnings();
        assertSQLState(CONSTRAINTDROPPED, w);
        assertFkConstraintExists(false, c, "t");
        doGrantRevoke(REVOKE, "test_dbo",
                      new String[] {g_r_c1, g_r_c2, g_r_c3}, "h");

        // {public, role}
        s.executeUpdate("grant h to DonaldDuck");
        setRole(c, "h");
        doGrantRevoke(GRANT, "test_dbo", g_r_c1, "public");
        doGrantRevoke(GRANT, "test_dbo", g_r_c2, "h");
        doGrantRevoke(GRANT, "test_dbo", g_r_c3, "h");
        cStmt.executeUpdate("alter table t add constraint fk " +
                            "foreign key(i,j,k) references s1.t1");

        s.executeUpdate("revoke h from DonaldDuck");
        w = s.getWarnings();
        assertSQLState(CONSTRAINTDROPPED, w);
        assertFkConstraintExists(false, c, "t");
        doGrantRevoke(REVOKE, "test_dbo", g_r_c1, "public");
        doGrantRevoke(REVOKE, "test_dbo", g_r_c2, "h");
        doGrantRevoke(REVOKE, "test_dbo", g_r_c3, "h");

        // {user, role}
        s.executeUpdate("grant h to DonaldDuck");
        setRole(c, "h");
        doGrantRevoke(GRANT, "test_dbo", g_r_c1, "DonaldDuck");
        doGrantRevoke(GRANT, "test_dbo", g_r_c2, "h");
        doGrantRevoke(GRANT, "test_dbo", g_r_c3, "h");
        cStmt.executeUpdate("alter table t add constraint fk " +
                            "foreign key(i,j,k) references s1.t1");
        s.executeUpdate("revoke h from DonaldDuck");
        w = s.getWarnings();
        assertSQLState(CONSTRAINTDROPPED, w);
        assertFkConstraintExists(false, c, "t");
        doGrantRevoke(REVOKE, "test_dbo", g_r_c1, "DonaldDuck");
        doGrantRevoke(REVOKE, "test_dbo", g_r_c2, "h");
        doGrantRevoke(REVOKE, "test_dbo", g_r_c3, "h");

        // {user, public, role}
        s.executeUpdate("grant h to DonaldDuck");
        setRole(c, "h");
        doGrantRevoke(GRANT, "test_dbo", g_r_c1, "DonaldDuck");
        doGrantRevoke(GRANT, "test_dbo", g_r_c2, "public");
        doGrantRevoke(GRANT, "test_dbo", g_r_c3, "h");
        cStmt.executeUpdate("alter table t add constraint fk " +
                            "foreign key(i,j,k) references s1.t1");
        s.executeUpdate("revoke h from DonaldDuck");
        w = s.getWarnings();
        assertSQLState(CONSTRAINTDROPPED, w);
        assertFkConstraintExists(false, c, "t");
        doGrantRevoke(REVOKE, "test_dbo", g_r_c1, "DonaldDuck");
        doGrantRevoke(REVOKE, "test_dbo", g_r_c2, "public");
        doGrantRevoke(REVOKE, "test_dbo", g_r_c3, "h");

        // Try the same as above but with EXECUTE privilege instead of
        // REFERENCES for a CHECK constraint
        s.executeUpdate("grant h to DonaldDuck");
        setRole(c, "h");
        doGrantRevoke(GRANT, "test_dbo", g_e, "h");
        cStmt.executeUpdate("alter table t add constraint ch " +
                            "check(i < s1.f1())");
        assertCheckConstraintExists(true, c, "t");
        s.executeUpdate("revoke h from DonaldDuck");
        w = s.getWarnings();
        assertSQLState(CONSTRAINTDROPPED, w);
        assertCheckConstraintExists(false, c, "t");
        doGrantRevoke(REVOKE, "test_dbo", g_e, "h");

        // Try the same as above but with two EXECUTE privileges
        s.executeUpdate("grant h to DonaldDuck");
        setRole(c, "h");
        doGrantRevoke(GRANT, "test_dbo", g_e, "h");
        doGrantRevoke(GRANT, "test_dbo", g_e_f2, "DonaldDuck");
        cStmt.executeUpdate("alter table t add constraint ch " +
                            "check(i < (s1.f1() + s1.f2()))");
        assertCheckConstraintExists(true, c, "t");
        s.executeUpdate("revoke h from DonaldDuck");
        w = s.getWarnings();
        assertSQLState(CONSTRAINTDROPPED, w);
        assertCheckConstraintExists(false, c, "t");
        doGrantRevoke(REVOKE, "test_dbo", g_e, "h");
        doGrantRevoke(REVOKE, "test_dbo", g_e_f2, "DonaldDuck");

        // Try the same as above but with multiple CHECK constraints to verify
        // that only those affected by a revoke are impacted.
        s.executeUpdate("grant h to DonaldDuck");
        setRole(c, "h");
        doGrantRevoke(GRANT, "test_dbo", g_e, "h");
        doGrantRevoke(GRANT, "test_dbo", g_e_f2, "DonaldDuck");
        cStmt.executeUpdate
            ("create table tmp(i int constraint ct1 check(i < s1.f1())," +
             "                 j int constraint ct2 check(j < s1.f2()))");
        s.executeUpdate("revoke h from DonaldDuck");

        // This should only impact ct1
        try {
            cStmt.executeUpdate("insert into tmp values (6, -1)");
        } catch (SQLException e) {
            fail("expected success", e);
        }

        try {
            cStmt.executeUpdate("insert into tmp values (6, 6)");
            fail("ct2 should remain");
        } catch (SQLException e) {
            assertSQLState(CHECKCONSTRAINTVIOLATED, e);
        }

        cStmt.executeUpdate("alter table tmp drop constraint ct2");
        doGrantRevoke(REVOKE, "test_dbo", g_e, "h");
        doGrantRevoke(REVOKE, "test_dbo", g_e_f2, "DonaldDuck");
        cStmt.executeUpdate("drop table tmp");

        cStmt.executeUpdate(dropTableString);

        cStmt.close();
        c.close();
        s.close();
        dboConn.close();
    }

    /**
     * DERBY-4191
     * There are times when no column is selected from a table in the from
     * list. At such a time, we should make sure that we make sure there
     * is atleast some kind of select privilege available on that table for
     * the query to succeed. eg of such queries
     * select count(*) from t1
     * select count(1) from t1
     * select 1 from t1
     * select t1.c1 from t1, t2
     * 
     * In addition, the subquery inside of a NON-select query should require
     * select privilege on the tables involved in the subquery eg
     * update dbo.t set a = ( select max(a1) + 2 from dbo.t1 )
     * update dbo.t set a = ( select max(b1) + 2 from dbo.t2 )
     * For both the queries above, in addition to update privilege requirement
     * on dbo.t(a), we need to require select privileges on columns/tables
     * within the select list. So for first query, the user should have select
     * privilege on dbo.t1 or dbo.t1(a1). Similarly, for 2nd query, the user
     * should have select privilege on dbo.t2 or dbo.t2(b1) 
     * @throws SQLException
     */
    public void testMinimumSelectPrivilege() throws SQLException {
        Connection dboConn = getConnection();
        Statement stmtDBO = dboConn.createStatement();

        Connection cDD = openUserConnection("DonaldDuck");
        Statement stmtDD = cDD.createStatement();

        Connection cMM = openUserConnection("MickeyMouse");
        Statement stmtMM = cMM.createStatement();

        stmtDBO.executeUpdate("create role role1");
        stmtDBO.executeUpdate("grant role1 to MickeyMouse");

        stmtDD.executeUpdate("create table DDtable1(c11 int, c12 int)");
        stmtDD.executeUpdate("insert into DDtable1 values(1, 2)");
        stmtDD.executeUpdate("create table DDtable2(c21 int, c22 int)");
        stmtDD.executeUpdate("insert into DDtable2 values(3, 4)");
        
        stmtMM.executeUpdate("set role role1");
        try {
        	stmtMM.executeQuery("select c11 from DonaldDuck.DDtable1");
        	fail("select should have failed");
        } catch (SQLException e) {
            assertSQLState("42502", e);
        }
        try {
        	stmtMM.executeUpdate("update DonaldDuck.DDtable1 set c11 = " +
        			" (select c21 from DonaldDuck.DDtable2)");
        	fail("select should have failed");
        } catch (SQLException e) {
            assertSQLState("42502", e);
        }

        stmtDD.executeUpdate("grant select(c12) on DDtable1 to role1");
        stmtDD.executeUpdate("grant update on DDtable1 to role1");
    	stmtMM.executeQuery("select c12 from DonaldDuck.DDtable1");
        try {
        	stmtMM.executeQuery("select c11 from DonaldDuck.DDtable1");
        	fail("select should have failed");
        } catch (SQLException e) {
            assertSQLState("42502", e);
        }
        try {
        	stmtMM.executeUpdate("update DonaldDuck.DDtable1 set c11 = " +
        			" (select c21 from DonaldDuck.DDtable2)");
        	fail("select should have failed");
        } catch (SQLException e) {
            assertSQLState("42502", e);
        }

        stmtDD.executeUpdate("grant select(c11) on DDtable1 to role1");
    	stmtMM.executeQuery("select c12 from DonaldDuck.DDtable1");
    	stmtMM.executeQuery("select c11 from DonaldDuck.DDtable1");
        try {
        	stmtMM.executeQuery("select c11 from DonaldDuck.DDtable1, " +
        			"DonaldDuck.DDtable2");
        	fail("select should have failed");
        } catch (SQLException e) {
            assertSQLState("42500", e);
        }
        try {
        	stmtMM.executeQuery("update DonaldDuck.DDtable1 set c11 = " +
        			" (select c21 from DonaldDuck.DDtable2)");
        	fail("select should have failed");
        } catch (SQLException e) {
            assertSQLState("42502", e);
        }

        stmtDD.executeUpdate("grant select(c21) on DDtable2 to role1");
    	stmtMM.executeQuery("select c12 from DonaldDuck.DDtable1");
    	stmtMM.executeQuery("select c11 from DonaldDuck.DDtable1");
    	stmtMM.executeQuery("select c11 from DonaldDuck.DDtable1, " +
    			"DonaldDuck.DDtable2");
    	stmtMM.executeUpdate("update DonaldDuck.DDtable1 set c11 = " +
    			" (select c21 from DonaldDuck.DDtable2)");
    }

    /**
     * Test that a prepared statement can no longer execute after its required
     * privileges acquired via the current role are no longer applicable.
     */
    public void testPSInvalidation() throws SQLException {
        Connection dboConn = getConnection();
        Statement s = dboConn.createStatement();

        Connection c = openUserConnection("DonaldDuck");
        Statement cStmt = c.createStatement();

        /*
        * 3-dimensional search space:
        *
        * Which role we grant the role to (direct to a role or to a role it
        * inherits)
        *    X
        * Whether the role is granted directly to the session user or to PUBLIC.
        *    X
        * Whether we grant the entire underlying table or just the column
        * needed.
        */
        String[] grantToThisRole = new String[] {"a2", "h"};
        String[] roleGrantees = new String[] {"DonaldDuck", "public"};
        String[][] privilegeStmts =
            new String[][] {{g_s, "select c1 from s1.t1"},
                            {g_s_c1, "select c1 from s1.t1"},
                            {g_e, "values s1.f1()"},
                            {g_u, "update s1.t1 set c1=0"},
                            {g_u_c1_c2_c3, "update s1.t1 set c1=0"},
                            {g_i, "insert into s1.t1 values (5,5,5)"}};

        PreparedStatement ps = null;

        for (int r = 0; r < grantToThisRole.length; r++) {
            for (int gNo = 0; gNo < roleGrantees.length; gNo++ ) {
                for (int i = 0; i < privilegeStmts.length; i++) {
                    /*
                     * Create a ps on the basis of a select privilege via a
                     * role.
                     */
                    s.executeUpdate("grant h to " + roleGrantees[gNo]);

                    doGrantRevoke(GRANT, "test_dbo", privilegeStmts[i][0],
                                  grantToThisRole[r]);

                    setRole(c, "h");
                    ps = c.prepareStatement(privilegeStmts[i][1]);

                    assertPsWorks(true, ps);

                    /*
                     * Setting another role should make the ps fail, since we
                     * no longer have the privilege.
                     */
                    setRole(c, "none");
                    assertPsWorks(false, ps);
                    // set it back:
                    setRole(c, "h");
                    assertPsWorks(true, ps);

                    /*
                     * Remove privileges from role, and the execute should
                     * fail.
                     */
                    doGrantRevoke(REVOKE, "test_dbo", privilegeStmts[i][0],
                                  grantToThisRole[r]);

                    assertPsWorks(false, ps);
                    doGrantRevoke(GRANT, "test_dbo", privilegeStmts[i][0],
                                  grantToThisRole[r]);

                    /*
                     * Revoking the role should also make the ps fail, since we
                     * no longer have the privilege.
                     */
                    setRole(c, "h");
                    assertPsWorks(true, ps);

                    s.executeUpdate("revoke h from " + roleGrantees[gNo]);
                    assertPsWorks(false, ps);

                    /*
                     * Check that prepared statements are reprepared if there
                     * is another applicable privilege, when the privilege
                     * granted via a role is used first and that role is
                     * revoked.
                     */
                    String[] directGrantee = roleGrantees;

                    // iterate over granting role h to {user, PUBLIC}
                    for (int u = 0; u < directGrantee.length; u++) {
                        s.executeUpdate("grant h to " + roleGrantees[gNo]);

                        setRole(c, "h");
                        assertPsWorks(true, ps);

                        doGrantRevoke(GRANT, "test_dbo",
                                      privilegeStmts[i][0],
                                      directGrantee[u]);

                        // Now we have select privilege two ways, via role and
                        // via user or PUBLIC.
                        // Now revoke role priv and see that ps is still
                        // unaffected.
                        s.executeUpdate("revoke h from " + roleGrantees[gNo]);
                        assertPsWorks(true, ps);

                        // Take away user privilege, too.
                        doGrantRevoke(REVOKE, "test_dbo",
                                      privilegeStmts[i][0],
                                      directGrantee[u]);
                        assertPsWorks(false, ps);
                    }

                    // clean up
                    doGrantRevoke(REVOKE, "test_dbo",privilegeStmts[i][0],
                                  grantToThisRole[r]);
                }
            }
        }

        /*
         * Dropping a role should also cause a dependent ps fail.
         *
         * (We do this test outside the loop above for simplicity of
         * reestablish role graph after the drop..)
         *
         */
        for (int i=0; i < privilegeStmts.length; i++) {
            doGrantRevoke(GRANT, "test_dbo", privilegeStmts[i][0], "h");
            s.executeUpdate("grant h to DonaldDuck");

            setRole(c, "h");
            ps = c.prepareStatement(privilegeStmts[i][1]);
            assertPsWorks(true, ps);

            s.executeUpdate("drop role h");
            assertPsWorks(false, ps);

            doGrantRevoke(REVOKE, "test_dbo", privilegeStmts[i][0], "h");

            // re-establish role graph
            s.executeUpdate("create role h");
            s.executeUpdate("grant e to h");
            s.executeUpdate("grant f to h");
        }

        cStmt.close();
        c.close();
        s.close();
        dboConn.close();
    }


    /**
     * Test behavior for when there are open result sets on prepared statements
     * that require privileges obtained via the current role and something
     * changes in the middle of accessing the result set. We should be able to
     * finish using the result set.
     */
    public void testOpenRs() throws SQLException {
        Connection dboConn = getConnection();
        Statement s = dboConn.createStatement();

        Connection c = openUserConnection("DonaldDuck");
        Statement cStmt = c.createStatement();
        ResultSet rs = null;
        String select = "select * from s1.t1";

        PreparedStatement ps = dboConn.prepareStatement(
            "insert into s1.t1 values (?,?,?)");
        for (int i=0; i < 5; i++) {
            ps.setInt(1, i);
            ps.setInt(2, i);
            ps.setInt(3, i);
            ps.execute();
        }


        /*
         * Select privilege revoked
         */
        // Auto-commit on
        doGrantRevoke(GRANT, "test_dbo", g_s, "h");
        s.execute("grant h to DonaldDuck");
        setRole(c, "h");
        rs = cStmt.executeQuery(select);

        rs.next();
        // Now remove privilege in middle of rs reading
        doGrantRevoke(REVOKE, "test_dbo", g_s, "h");

        // check that we can read the next row
        rs.next();
        rs.close();

        // Auto-commit off
        c.setAutoCommit(false);
        doGrantRevoke(GRANT, "test_dbo", g_s, "h");
        setRole(c, "h");
        rs = cStmt.executeQuery(select);

        rs.next();
        c.commit();
        // Now remove privilege in middle of rs reading
        doGrantRevoke(REVOKE, "test_dbo", g_s, "h");

        // check that we can read the next row
        rs.next();
        rs.close();
        c.setAutoCommit(true);

        /*
         * Role privilege revoked
         */
        // Auto-commit on
        doGrantRevoke(GRANT, "test_dbo", g_s, "h");
        s.execute("grant h to DonaldDuck");
        setRole(c, "h");
        rs = cStmt.executeQuery(select);

        rs.next();
        // Now remove privilege in middle of rs reading
        s.execute("revoke h from DonaldDuck");

        // check that we can read the next row
        rs.next();
        rs.close();

        // Auto-commit off
        c.setAutoCommit(false);
        s.execute("grant h to DonaldDuck");
        setRole(c, "h");
        rs = cStmt.executeQuery(select);

        rs.next();
        c.commit();
        // Now remove privilege in middle of rs reading
        s.execute("revoke h from DonaldDuck");

        // check that we can read the next row
        rs.next();
        rs.close();
        c.setAutoCommit(true);
        doGrantRevoke(REVOKE, "test_dbo", g_s, "h");


        /*
         * Current role changed
         */
        // Auto-commit on
        doGrantRevoke(GRANT, "test_dbo", g_s, "h");
        s.execute("grant h to DonaldDuck");
        setRole(c, "h");
        c.setAutoCommit(true);
        rs = cStmt.executeQuery(select);

        rs.next();
        // Now change role in middle of rs reading
        setRole(c, "none");

        // check that we can read the next row
        rs.next();
        rs.close();

        // Auto-commit off
        c.setAutoCommit(false);
        setRole(c, "h");
        rs = cStmt.executeQuery(select);

        rs.next();
        // Now remove privilege in middle of rs reading
        c.commit();
        setRole(c, "none");

        // check that we can read the next row
        rs.next();
        rs.close();
        c.setAutoCommit(true);
        doGrantRevoke(REVOKE, "test_dbo", g_s, "h");

        // clean up
        s.executeUpdate("delete from s1.t1");
        c.close();
        dboConn.close();
    }


    /**
     * Test that DEFAULT CURRENT_ROLE works as expected
     * See DERBY-3897.
     */
    public void testDefaultCurrentRole() throws SQLException {
        Connection dboConn = getConnection();
        Statement s = dboConn.createStatement();
        s.execute("grant h to DonaldDuck");

        Connection c = openUserConnection("DonaldDuck");
        Statement cStmt = c.createStatement();
        setRole(c, "h");

        // CREATE TABLE
        cStmt.executeUpdate
            ("create table t(role varchar(128) default current_role)");
        cStmt.executeUpdate("insert into t values default");
        ResultSet rs = cStmt.executeQuery("select * from t");
        JDBC.assertSingleValueResultSet(rs, "\"H\"");
        rs.close();
        cStmt.executeUpdate("drop table t");

        // ALTER TABLE
        cStmt.executeUpdate("create table t(i int)");
        cStmt.executeUpdate("insert into t values 1");
        cStmt.executeUpdate
            ("alter table t " +
             "add column role varchar(10) default current_role");
        rs = cStmt.executeQuery("select * from t");
        JDBC.assertFullResultSet(rs, new String[][]{{"1",  "\"H\""}});
        rs.close();
        cStmt.executeUpdate("drop table t");

        // do the same from within a stored procedure

        s.execute("grant execute on procedure s1.calledNested to DonaldDuck");

        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.
            cStmt.executeUpdate("call s1.calledNested()");
        }

        setRole(c, "none");

        cStmt.close();
        s.execute("revoke h from DonaldDuck");
        s.execute("revoke execute on procedure s1.calledNested " +
                  "from DonaldDuck restrict");
        s.close();

        c.close();
        dboConn.close();
    }


    /**
     * Test that CURRENT_ROLE works as expected in some miscellaneous contexts.
     * See DERBY-3897.
     */
    public void testCurrentRoleInWeirdContexts() throws SQLException {
        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.
            return;
        }

        Connection dboConn = getConnection();
        Statement s = dboConn.createStatement();
        setRole(dboConn, "a1");
        s.execute("create table trackCreds(usr varchar(30), role varchar(30))");
        s.executeUpdate("create table t(i int)");
        s.execute("grant insert on t to DonaldDuck");
        s.execute("grant h to DonaldDuck");

        // From within a trigger body:
        s.execute("create trigger tr after insert on t " +
                  "insert into trackCreds values (current_user, current_role)");

        Connection c = openUserConnection("DonaldDuck");
        Statement cStmt = c.createStatement();
        setRole(c, "h");
        cStmt.executeUpdate("insert into test_dbo.t values 1");

        ResultSet rs = s.executeQuery("select * from trackCreds");
        JDBC.assertFullResultSet(rs, new String[][]{{"DONALDDUCK",  "\"H\""}});
        rs.close();
        setRole(c, "none");
        cStmt.close();

        // From within a CHECK constraint, that we get an error
        try {
            s.execute("create table strange(role varchar(30) " +
                      "check (role = current_role))");
            fail("current_role inside a check constraint should be denied");
        } catch (SQLException e) {
            assertSQLState(UNRELIABLE, e);
        }

        // From within a function, called from a CHECK constraint
        // executed as a substatement as part of ALTER TABLE.
        // In this case, the session context stack contains two elements
        // referenced from the three activations thus:
        //
        // top level "alter table" act.          -> top level session context
        // substatement (check constraint act.)  -> top level session context
        // nested connnection in getCurrentRole,
        //            "values current_role" act. -> pushed session context
        //
        // Before DERBY-3897 the call to s1.getCurrentRole would yield null
        // because the pushed session context for getCurrentRole would inherit
        // a wrong (newly created) session context from the CHECK constraint
        // substatement's activation. After DERBY-3897, the substatement
        // correctly inherits the session context of "alter table"s
        // activation, so the pushed session context for getCurrentRole will
        // be correct, too.
        //
        s.execute("create table strange(i int)");
        s.execute("insert into strange values null");
        s.execute("alter table strange " +
                  "add constraint s check (s1.getCurrentRole() = '\"A1\"')");

        s.execute("revoke h from DonaldDuck");
        s.execute("revoke insert on t from DonaldDuck");
        setRole(dboConn, "none");
        s.execute("drop table trackCreds");
        s.execute("drop table t");
        s.execute("drop table strange");
        s.close();

        c.close();
        dboConn.close();
    }



    /**
     * stored function: s1.f1
     */
    public static int s1f1() {
        return 0;
    }


    private void assertAllforRole(int hasPrivilege,
                                  Connection c,
                                  String grantee) throws SQLException {
        for (int i=0; i < grantRevokes.length; i++) {
            doGrantRevoke(GRANT, "test_dbo", grantRevokes[i], grantee);
            assertEverything(hasPrivilege, c, null);
            doGrantRevoke(REVOKE, "test_dbo", grantRevokes[i], grantee);

            // check that when priv is revoked we no longer have it
            if (hasPrivilege == VIAROLE) {
                assertEverything(NOPRIV, c, null);
            }

        }
    }


    private void assertEverything(int hasPrivilege,
                                  String user,
                                  String role) throws SQLException {

        Connection c = openUserConnection(user);
        assertEverything(hasPrivilege, c, role);
        c.close();
    }


    private void assertEverything(int hasPrivilege,
                                  Connection c,
                                  String role) throws SQLException {
        if (role != null) {
            setRole(c, role);
        }

        String[] columns = new String[] {"c1", "c2"};
        String schema = "s1";
        String table = "t1";
        String function = "f1";

        assertSelectPrivilege
            (hasPrivilege, c, schema, table, columns);
        assertSelectPrivilege
            (hasPrivilege, c, schema, table, null);
        assertInsertPrivilege
            (hasPrivilege, c, schema, table, null);
        assertUpdatePrivilege
            (hasPrivilege, c, schema, table, columns);
        assertUpdatePrivilege
            (hasPrivilege, c, schema, table, null);
        assertDeletePrivilege
            (hasPrivilege, c, schema, table);
        assertReferencesPrivilege
            (hasPrivilege, c, schema, table, columns);
        assertReferencesPrivilege
            (hasPrivilege, c, schema, table, null);
        assertTriggerPrivilege
            (hasPrivilege, c, schema, table);
        assertExecutePrivilege(hasPrivilege, c, schema, function);
    }


    /**
     * Assert that a user has execute privilege on a given function
     *
     * @param hasPrivilege whether or not the user has the privilege
     * @param user the user to check
     * @param role to use, or null if we do not want to set the role
     * @param schema the schema to check
     * @param function the name of the function to check
     * @throws SQLException throws all exceptions
     */
    private void assertExecutePrivilege(int hasPrivilege,
                                        String user,
                                        String role,
                                        String schema,
                                        String function) throws SQLException {
        Connection c = openUserConnection(user);

        if (role != null) {
            setRole(c, role);
        }

        assertExecutePrivilege(hasPrivilege, c, schema, function);
        c.close();
    }


    /**
     * Assert that a user has execute privilege on a given function
     *
     * @param hasPrivilege whether or not the user has the privilege
     * @param c connection to use
     * @param schema the schema to check
     * @param function the name of the function to check
     * @throws SQLException throws all exceptions
     */
    private void assertExecutePrivilege(int hasPrivilege,
                                        Connection c,
                                        String schema,
                                        String function) throws SQLException {
        Statement stm = c.createStatement();

        try {
            ResultSet rs =
                stm.executeQuery("values " + schema + "." + function + "()");

            rs.next();
            rs.close();
            stm.close();

            if (hasPrivilege == NOPRIV) {
                fail("expected no EXECUTE privilege on function. " +
                     formatArgs(c, schema, function));
            }
        } catch (SQLException e) {
            if (stm != null) {
                stm.close();
            }

            if (hasPrivilege == NOPRIV)
                assertSQLState(NOEXECUTEPERMISSION, e);
            else {
                fail("Unexpected lack of execute privilege. " +
                     formatArgs(c, schema, function), e);
            }
        }
    }


    /**
     * Assert that a user has trigger privilege on a given table
     *
     * @param hasPrivilege whether or not the user has the privilege
     * @param user the user to check
     * @param role to use, or null if we do not want to set the role
     * @param schema the schema to check
     * @param table the name of the table to check
     * @throws SQLException throws all exceptions
     */
    private void assertTriggerPrivilege(int hasPrivilege,
                                        String user,
                                        String role,
                                        String schema,
                                        String table) throws SQLException {
        Connection c = openUserConnection(user);

        if (role != null) {
            setRole(c, role);
        }

        assertTriggerPrivilege(hasPrivilege, c, schema, table);
        c.close();
    }


    /**
     * Assert that a user has trigger execute privilege on a given table /
     * column set.
     *
     * @param hasPrivilege whether or not the user has the privilege
     * @param c connection to use
     * @param schema the schema to check
     * @param table the table to check
     * @throws SQLException throws all exceptions
     */
    private void assertTriggerPrivilege(int hasPrivilege,
                                        Connection c,
                                        String schema,
                                        String table) throws SQLException {
        Statement s = c.createStatement();
        String triggerName = table + "Trigger";

        try {
            int i = s.executeUpdate
                ("create trigger " + triggerName + " after insert on " +
                 schema + "." + table + " for each row values 1");

            if (hasPrivilege != NOPRIV) {
                assertEquals(0, i);
            }

            s.execute("drop trigger " + triggerName);

            if (hasPrivilege == NOPRIV) {
                fail("expected no TRIGGER privilege on table. " +
                     formatArgs(c, schema, table));
            }
        } catch (SQLException e) {
            if (hasPrivilege == NOPRIV) {
                assertSQLState(NOTABLEPERMISSION, e);
            } else {
                fail("Unexpected lack of trigger privilege. " +
                     formatArgs(c, schema, table), e);
            }
        }

        s.close();

        assertPrivilegeMetadata
            (hasPrivilege, c, "TRIGGER", schema, table, null);

    }


    /**
     * Assert that a user has references privilege on a given table
     *
     * @param hasPrivilege whether or not the user has the privilege
     * @param user the user to check
     * @param role to use, or null if we do not want to set the role
     * @param schema the schema to check
     * @param table the name of the table to check
     * @param columns the name of the columns to check, or null
     * @throws SQLException throws all exceptions
     */
    private void assertReferencesPrivilege(int hasPrivilege,
                                           String user,
                                           String role,
                                           String schema,
                                           String table,
                                           String[] columns)
            throws SQLException {

        Connection c = openUserConnection(user);

        if (role != null) {
            setRole(c, role);
        }

        assertReferencesPrivilege(hasPrivilege, c, schema, table, columns);
        c.close();
    }


    /**
     * Assert that a user has references privilege on a given table / column
     * set.
     *
     * @param hasPrivilege whether or not the user has the privilege
     * @param c connection to use
     * @param schema the schema to check
     * @param table the table to check
     * @param columns the set of columns to check
     * @throws SQLException throws all exceptions
     */
    private void assertReferencesPrivilege(int hasPrivilege,
                                           Connection c,
                                           String schema,
                                           String table,
                                           String[] columns)
            throws SQLException {

        Statement s = c.createStatement();

        columns = ((columns == null)
                   ? getAllColumns(schema, table)
                   : columns);

        for (int i = 0; i < columns.length; i++) {
            try {
                s.execute("create table referencestest (c1 int" +
                          " references " + schema + "." +
                          table + "(" + columns[i] + "))" );

                s.execute("drop table referencestest");

                if (hasPrivilege == NOPRIV) {
                    fail("Unexpected references privilege. " +
                         formatArgs(c, schema, table,
                                    new String[]{columns[i]}));
                }
            } catch (SQLException e) {
                if (hasPrivilege == NOPRIV) {
                    assertSQLState(NOCOLUMNPERMISSION, e);
                } else {
                    fail("Unexpected lack of references privilege. " +
                         formatArgs(c, schema, table,
                                    new String[]{columns[i]}),
                         e);
                }
            }
        }

        s.close();

        assertPrivilegeMetadata
            (hasPrivilege, c, "REFERENCES", schema, table, columns);
    }


    /**
     * Assert that a user has update privilege on a given table
     *
     * @param hasPrivilege whether or not the user has the privilege
     * @param user the user to check
     * @param role to use, or null if we do not want to set the role
     * @param schema the schema to check
     * @param table the name of the table to check
     * @param columns the name of the columns to check, or null
     * @throws SQLException throws all exceptions
     */
    private void assertUpdatePrivilege(int hasPrivilege,
                                       String user,
                                       String role,
                                       String schema,
                                       String table,
                                       String[] columns)
            throws SQLException {

        Connection c = openUserConnection(user);

        if (role != null) {
            setRole(c, role);
        }

        assertUpdatePrivilege(hasPrivilege, c, schema, table, columns);
        c.close();
    }


    /**
     * Assert that a user has update privilege on a given table / column set.
     *
     * @param hasPrivilege whether or not the user has the privilege
     * @param c connection to use
     * @param schema the schema to check
     * @param table the table to check
     * @param columns the set of columns to check
     * @throws SQLException throws all exceptions
     */
    private void assertUpdatePrivilege(int hasPrivilege,
                                       Connection c,
                                       String schema,
                                       String table,
                                       String[] columns)
            throws SQLException {

        String[] checkColumns =
            (columns == null) ? getAllColumns(schema, table) : columns;

        Statement s = c.createStatement();
        int columnCount = 0;
        boolean checkCount;

        for (int i = 0; i < checkColumns.length; i++) {
            checkCount = false;

            try {
                // Try to get count of rows to verify update rows. We may not
                // have select privilege on the column, in which case, we
                // simply don't verify the count.
                try {
                    ResultSet countRS =
                        s.executeQuery("select count(" + checkColumns[i] +
                                       ") from " + schema + "." + table);

                    if (!countRS.next()) {
                        fail("Could not get count on " + checkColumns[i] +
                             " to verify update");
                    }

                    columnCount = countRS.getInt(1);
                    checkCount = true;
                } catch (SQLException e) {
                    assertSQLState(NOCOLUMNPERMISSION, e);
                }

                int actualCount =
                    s.executeUpdate("update " + schema + "." + table +
                                    " set " + checkColumns[i] + "= 0");

                if (hasPrivilege != NOPRIV && checkCount) {
                    // update count should equal select count
                    assertEquals(columnCount, actualCount);
                }

                if (hasPrivilege == NOPRIV) {
                    fail("expected no UPDATE privilege on  " +
                         formatArgs(c, schema, table,
                                    new String[]{checkColumns[i]}));
                }
            } catch (SQLException e) {
                if (hasPrivilege == NOPRIV) {
                    assertSQLState(NOCOLUMNPERMISSION, e);
                } else {
                    fail("Unexpected lack of privilege to update. " +
                         formatArgs(c, schema, table,
                                    new String[]{checkColumns[i]}));
                }
            }
        }

        s.close();

        assertPrivilegeMetadata
            (hasPrivilege, c, "UPDATE", schema, table, columns);
    }


    /**
     * Assert that a user has insert privilege on a given table
     *
     * @param hasPrivilege whether or not the user has the privilege
     * @param user the user to check
     * @param role to use, or null if we do not want to set the role
     * @param schema the schema to check
     * @param table the name of the table to check
     * @param columns the name of the columns to check, or null
     * @throws SQLException throws all exceptions
     */
    private void assertInsertPrivilege(int hasPrivilege,
                                       String user,
                                       String role,
                                       String schema,
                                       String table,
                                       String[] columns)
            throws SQLException {

        Connection c = openUserConnection(user);

        if (role != null) {
            setRole(c, role);
        }

        assertInsertPrivilege(hasPrivilege, c, schema, table, columns);
        c.close();
    }


    /**
     * Assert that a user has insert privilege on a given table / column set.
     *
     * @param hasPrivilege whether or not the user has the privilege
     * @param c connection to use
     * @param schema the schema to check
     * @param table the table to check
     * @param columns the set of columns to check
     * @throws SQLException throws all exceptions
     */
    private void assertInsertPrivilege(int hasPrivilege,
                                       Connection c,
                                       String schema,
                                       String table,
                                       String[] columns)
            throws SQLException {

        Statement s = c.createStatement();

        try {
            int i = s.executeUpdate("insert into " + schema + "." +
                                    table + " values (0,0,0)");
            if (hasPrivilege == NOPRIV) {
                fail("expected no INSERT privilege on table, " +
                     formatArgs(c, schema, table, columns));
            }
        } catch (SQLException e) {
            if (hasPrivilege == NOPRIV) {
                assertSQLState(NOTABLEPERMISSION, e);
            } else {
                fail("Unexpected lack of insert privilege. " +
                     formatArgs(c, schema, table, columns), e);
            }
        }

        s.close();

        assertPrivilegeMetadata
            (hasPrivilege, c, "INSERT", schema, table, columns);
    }


    /**
     * Assert that a user has select privilege on a given table
     *
     * @param hasPrivilege whether or not the user has the privilege
     * @param user the user to check
     * @param role to use, or null if we do not want to set the role
     * @param schema the schema to check
     * @param table the name of the table to check
     * @param columns the name of the columns to check, or null
     * @throws SQLException throws all exceptions
     */
    private void assertSelectPrivilege(int hasPrivilege,
                                       String user,
                                       String role,
                                       String schema,
                                       String table,
                                       String[] columns)
            throws SQLException {

        Connection c = openUserConnection(user);

        if (role != null) {
            setRole(c, role);
        }

        assertSelectPrivilege(hasPrivilege, c, schema, table, columns);
        c.close();
    }


    /**
     * Assert that a user has select privilege on a given table / column set.
     *
     * @param hasPrivilege whether or not the user has the privilege
     * @param c connection to use
     * @param schema the schema to check
     * @param table the table to check
     * @param columns the set of columns to check
     * @throws SQLException throws all exceptions
     */
    private void assertSelectPrivilege(int hasPrivilege,
                                       Connection c,
                                       String schema,
                                       String table,
                                       String[] columns) throws SQLException {
      assertSelectPrivilege(hasPrivilege, c, schema, 
        		table, columns, NOCOLUMNPERMISSION);
      assertSelectConstantPrivilege(hasPrivilege, c, schema, 
        		table, NOTABLEPERMISSION);
      assertSelectCountPrivilege(hasPrivilege, c, schema, 
        		table, columns, NOTABLEPERMISSION);
    }

    /**
     * Assert that a user has select privilege at the table(s) level  or 
     * atleast on one column from each of the tables involved in the 
     * query when running a select query which selects count(*) or
     * count(constant) from the tables.
     *
     * @param hasPrivilege whether or not the user has the privilege
     * @param c connection to use
     * @param schema the schema to check
     * @param table the table to check
     * @param columns used for error handling if ran into exception
     * @param sqlState expected state if hasPrivilege == NOPRIV
     * @throws SQLException throws all exceptions
     */
    private void assertSelectCountPrivilege(int hasPrivilege,
                                       Connection c,
                                       String schema,
                                       String table,
                                       String[] columns,
                                       String sqlState) throws SQLException {
        Statement s = c.createStatement();

        try {
            s.execute("select count(*) from " + schema + "." + table);

            if (hasPrivilege == NOPRIV) {
                fail("expected no SELECT privilege on table " +
                     formatArgs(c, schema, table, columns));
            }
        } catch (SQLException e) {
            if (hasPrivilege == NOPRIV) {
                assertSQLState(sqlState, e);
            } else {
                fail("Unexpected lack of select privilege. " +
                     formatArgs(c, schema, table, columns), e);
            }
        }

        try {
            s.execute("select count('a') from " + schema + "." + table);

            if (hasPrivilege == NOPRIV) {
                fail("expected no SELECT privilege on table " +
                     formatArgs(c, schema, table, columns));
            }
        } catch (SQLException e) {
            if (hasPrivilege == NOPRIV) {
                assertSQLState(sqlState, e);
            } else {
                fail("Unexpected lack of select privilege. " +
                     formatArgs(c, schema, table, columns), e);
            }
        }

        s.close();
    }

    /**
     * Assert that a user has select privilege at the table(s) level  or 
     * atleast on one column from each of the tables involved in the 
     * query when running a select query which only selects constants from 
     * the tables.
     *
     * @param hasPrivilege whether or not the user has the privilege
     * @param c connection to use
     * @param schema the schema to check
     * @param table the table to check
     * @param sqlState expected state if hasPrivilege == NOPRIV
     * @throws SQLException throws all exceptions
     */
    private void assertSelectConstantPrivilege(int hasPrivilege,
                                       Connection c,
                                       String schema,
                                       String table,
                                       String sqlState) throws SQLException {
        Statement s = c.createStatement();

        try {
            s.execute("select 1 from " + schema + "." + table);

            if (hasPrivilege == NOPRIV) {
                fail("expected no SELECT privilege on table " +
                     formatArgs(c, schema, table));
            }
        } catch (SQLException e) {
            if (hasPrivilege == NOPRIV) {
                assertSQLState(sqlState, e);
            } else {
                fail("Unexpected lack of select privilege. " +
                     formatArgs(c, schema, table), e);
            }
        }

        s.close();
    }


    /**
     * Assert that a user has select privilege on a given table / column set.
     *
     * @param hasPrivilege whether or not the user has the privilege
     * @param c connection to use
     * @param schema the schema to check
     * @param table the table to check
     * @param columns the set of columns to check
     * @param sqlState expected state if hasPrivilege == NOPRIV
     * @throws SQLException throws all exceptions
     */
    private void assertSelectPrivilege(int hasPrivilege,
                                       Connection c,
                                       String schema,
                                       String table,
                                       String[] columns,
                                       String sqlState) throws SQLException {
        Statement s = c.createStatement();

        try {
            s.execute("select " + columnListAsString(columns) +
                      " from " + schema + "." + table);

            if (hasPrivilege == NOPRIV) {
                fail("expected no SELECT privilege on table " +
                     formatArgs(c, schema, table, columns));
            }
        } catch (SQLException e) {
            if (hasPrivilege == NOPRIV) {
                assertSQLState(sqlState, e);
            } else {
                fail("Unexpected lack of select privilege. " +
                     formatArgs(c, schema, table, columns), e);
            }
        }

        s.close();

        assertPrivilegeMetadata
            (hasPrivilege, c, "SELECT", schema, table, columns);
    }


    /**
     * Check that a given view exists (select privilege assumed) or not by
     * selecting from it. The connection user must supposed to be the owner for
     * this to work.
     */
    private void assertViewExists(boolean exists,
                                  Connection c,
                                  String table) throws SQLException {
        Statement s = c.createStatement();
        try {
            s.execute("select * from " + table);
            if (!exists) {
                fail("Table expected not to exist: " + table);
            }
         } catch (SQLException e) {
             if (exists) {
                 fail("Table expected to exist: " + table, e);
             }
             assertSQLState(TABLENOTFOUND, e);
         }
         s.close();
    }


    /**
     * Check that a given trigger exists (select privilege assumed) or not.
     * NOTE: It is destructive, since the test is by dropping the trigger.  The
     * connection user must supposed to be the owner for this to work.
     */
    private void assertTriggerExists(boolean exists,
                                     Connection c,
                                     String trigger) throws SQLException {
        Statement s = c.createStatement();
        try {
            s.execute("drop trigger " + trigger);
            if (!exists) {
                fail("Trigger expected not to exist: " + trigger);
            }
         } catch (SQLException e) {
             if (exists) {
                 fail("Trigger expected to exist: " + trigger, e);
             }
             assertSQLState(OBJECTNOTFOUND, e);
         }
         s.close();
    }


    /**
     * Check that a given foregin key constraint exists by the following
     * method: We insert a value that is not present in the referenced table so
     * the foreign key constraint will fail if the constraint is present. The
     * connection user must be the owner for this to work.
     */
    private void assertFkConstraintExists(boolean exists,
                                        Connection c,
                                        String table)
            throws SQLException {
        assertConstraintExists(exists, c, table, FKVIOLATION);
    }

    /**
     * Check that a given check constraint exists by the following method: We
     * insert a value that does not satify the check constraint. The connection
     * user must be the owner for this to work.
     */
    private void assertCheckConstraintExists(boolean exists,
                                        Connection c,
                                        String table)
            throws SQLException {
        assertConstraintExists(exists, c, table, CHECKCONSTRAINTVIOLATED);
    }


    private void assertConstraintExists(boolean exists,
                                        Connection c,
                                        String table,
                                        String sqlState)
            throws SQLException {

        Statement s = c.createStatement();
        try {
            s.execute("insert into " + table + " values (6,6,6)");
            s.execute("delete from " + table);

            if (exists) {
                fail("Table expected to have a constraint: " + table);
            }
         } catch (SQLException e) {
             if (!exists) {
                 fail("Table expected not to have a constraint: " + table, e);
             }
             assertSQLState(sqlState, e);
         }
         s.close();
    }


    /**
     * Check that a given prepared statement can be executed.
     */
    private void assertPsWorks(boolean works,
                               PreparedStatement ps) throws SQLException {

        ps.getConnection().setAutoCommit(false);

        try {
            boolean b = ps.execute();
            ResultSet rs = ps.getResultSet();
            if (rs != null) {
                rs.next();
                rs.close();
            }
            ps.getConnection().rollback();
            ps.getConnection().setAutoCommit(true);

            if (!works) {
                fail("Prepared statement expected to fail.");
            }
         } catch (SQLException e) {
            ps.getConnection().setAutoCommit(true);

            if (works) {
                 fail("Prepared statement expected to work.", e);
             }
             assertSQLState
                 (new String[]{NOCOLUMNPERMISSION,
                               NOEXECUTEPERMISSION,
                               NOTABLEPERMISSION},
                  e);
         }
    }


    /**
     * Assert that a user has delete privilege on a given table
     *
     * @param hasPrivilege whether or not the user has the privilege
     * @param user the user to check
     * @param role to use, or null if we do not want to set the role
     * @param schema the schema to check
     * @param table the name of the table to check
     * @throws SQLException throws all exceptions
     */
    private void assertDeletePrivilege(int hasPrivilege,
                                       String user,
                                       String role,
                                       String schema,
                                       String table)
            throws SQLException {

        Connection c = openUserConnection(user);

        if (role != null) {
            setRole(c, role);
        }

        assertDeletePrivilege(hasPrivilege, c, schema, table);
        c.close();
    }


    /**
     * Assert that a user has delete privilege on a given table.
     *
     * @param hasPrivilege whether or not the user has the privilege
     * @param c connection to use
     * @param schema the schema to check
     * @param table the table to check
     * @throws SQLException throws all exceptions
     */
    private void assertDeletePrivilege(int hasPrivilege,
                                       Connection c,
                                       String schema,
                                       String table) throws SQLException {

        Statement s = c.createStatement();

        try {
            s.execute("delete from " + schema + "." + table);

            if (hasPrivilege == NOPRIV) {
                fail("expected no DELETE privilege on table " +
                     formatArgs(c, schema, table));
            }
        } catch (SQLException e) {
            if (hasPrivilege == NOPRIV) {
                assertSQLState(NOTABLEPERMISSION, e);
            } else {
                fail("Unexpected lack of delete privilege. " +
                     formatArgs(c, schema, table), e);
            }
        }

        s.close();

        assertPrivilegeMetadata
            (hasPrivilege, c, "DELETE", schema, table, null);
    }

    /**
     * Assert that a specific privilege exists by checking the
     * database metadata available to a user.
     *

     * @param hasPrivilege Is != NOPRIV if we expect the caller to have the
     *                      privilege
     * @param c user connection
     * @param type type of privilege, e.g. SELECT, INSERT, DELETE, etc.
     * @param schema the schema to check
     * @param table the table to check
     * @param columns the set of columns to check, or all columns if null
     * @throws SQLException
     */
    private void assertPrivilegeMetadata(int hasPrivilege,
                                         Connection c,
                                         String type,
                                         String schema,
                                         String table,
                                         String[] columns) throws SQLException {
        ResultSet rs;
        Statement stm = c.createStatement();
        rs = stm.executeQuery("values current_user");
        rs.next();
        String user = rs.getString(1);
        rs.close();
        stm.close();

        if (isOwner(schema, user)) {
            //  NOTE: Does not work for table owner, who has no manifest entry
            //  corresponding to the privilege in SYSTABLEPERMS.
            return;
        }

        if (hasPrivilege == VIAROLE) {
            // No DatabaseMetaData for roles. We could of course check
            // SYS.SYSROLES and the privilege tables but then we would have to
            // essentially rebuild the whole role privilege computation
            // machinery in this test.. ;)
            return;
        }

        DatabaseMetaData dm = c.getMetaData();

        rs = dm.getTablePrivileges
            (null, JDBC.identifierToCNF(schema), JDBC.identifierToCNF(table));

        boolean found = false;

        // check getTablePrivileges
        if (columns == null) {
            while (rs.next())
                {
                    // Also verify that grantor and is_grantable can be
                    // obtained Derby doesn't currently support the for grant
                    // option, the grantor is always the object owner - in this
                    // test, test_dbo, and is_grantable is always 'NO'.
                    assertEquals(JDBC.identifierToCNF("test_dbo"),
                                 rs.getString(4));
                    assertEquals("NO", rs.getString(7));

                    if (rs.getString(6).equals(type)) {
                        String privUser = rs.getString(5);

                        if (privUser.equals(user) ||
                                privUser.equals(
                                    JDBC.identifierToCNF("public"))) {
                            found = true;
                        }
                    }
                }
            assertEquals(hasPrivilege == VIAUSER, found);
            rs.close();
        }

        // check getColumnPrivileges()
        ResultSet cp = null;
        if (columns == null) {
            /*
             * Derby does not record table level privileges in SYSCOLPERMS, so
             * the following does not work. If it is ever changed so that
             * getColumnPrivileges returns proper results for table level
             * privileges, this(*) can be reenabled.
             *
             * (*) See GrantRevokeTest.
             */
        } else {
            // or, check that all given columns have privilege or not as the
            // case may be
            int noFound = 0;

            for (int i = 0; i < columns.length; i++) {
                cp = dm.getColumnPrivileges(null,
                                            JDBC.identifierToCNF(schema),
                                            JDBC.identifierToCNF(table),
                                            JDBC.identifierToCNF(columns[i]));

                while (cp.next()) {
                    // also verify that grantor and is_grantable are valid
                    // Derby doesn't currently support for grant, so
                    // grantor is always the object owner - in this test,
                    // test_dbo, and getColumnPrivileges casts 'NO' for
                    // is_grantable for supported column-related privileges
                    assertEquals(JDBC.identifierToCNF("test_dbo"),
                                 cp.getString(5));
                    assertEquals("NO", cp.getString(8));

                    if (cp.getString(7).equals(type)) {
                        String privUser = cp.getString(6);

                        if (privUser.equals(user) ||
                                privUser.equals(
                                    JDBC.identifierToCNF("public"))) {
                            noFound++;
                        }
                    }
                }
            }

            if (hasPrivilege == VIAUSER) {
                assertEquals(columns.length, noFound);
            } else {
                assertEquals(0, noFound);
            }
        }

        if (cp != null) {
            cp.close();
        }
    }

    private boolean isOwner(String schema, String user) throws SQLException {
        Connection c = getConnection();
        Statement stm = c.createStatement();
        ResultSet rs = stm.executeQuery
            ("select schemaname, authorizationid from sys.sysschemas " +
             "where schemaname='" + JDBC.identifierToCNF(schema) + "'");
        rs.next();
        boolean result = rs.getString(2).equals(JDBC.identifierToCNF(user));
        rs.close();
        stm.close();
        return result;
    }


    /**
     * Get all the columns in a given schema / table
     *
     * @return an array of Strings with the column names
     * @throws SQLException
     */
    private String[] getAllColumns(String schema, String table)
            throws SQLException
    {
        Connection c = getConnection();
        DatabaseMetaData dbmd = c.getMetaData();
        ArrayList columnList = new ArrayList();
        ResultSet rs =
            dbmd.getColumns( (String) null, schema, table, (String) null);

        while(rs.next())
            {
                columnList.add(rs.getString(4));
            }

        return (String[]) columnList.toArray(new String[]{});
    }

    /**
     * Return the given String array as a comma separated String
     *
     * @param columns an array of columns to format
     * @return a comma separated String of the column names
     */
    private static String columnListAsString(String[] columns) {
        if (columns == null) {
            return "*";
        }

        StringBuffer sb = new StringBuffer(columns[0]);

        for (int i = 1; i < columns.length; i++ ) {
            sb.append("," + columns[i]);
        }

        return sb.toString();
    }

    /**
     * Format the table arguments used by the various assert* methods for
     * printing.
     */
    private static String formatArgs(Connection c,
                                     String schema,
                                     String table,
                                     String[] columns) throws SQLException {

        return
            formatArgs(c, schema, table) +
            "(" + columnListAsString(columns) + ")";
    }


    /**
     * Format the dbObject arguments used by the various assert* methods for
     * printing.
     */
    private static String formatArgs(Connection c,
                                     String schema,
                                     String dbObject)  throws SQLException {

        ResultSet rs;
        Statement stm =  c.createStatement();
        rs = stm.executeQuery("values current_user");
        rs.next();
        String user = rs.getString(1);

        rs = c.createStatement().executeQuery("values current_role");
        rs.next();
        String role = rs.getString(1);
        rs.close();
        stm.close();

        return
            "User: " + user +
            (role == null ? "" : " Role: " + role) +
            " Object: " +
            schema + "." +
            dbObject;
    }

    /**
     * Set the given role for the current session.
     */
    private void setRole(Connection c, String role) throws SQLException {
        PreparedStatement ps;

        if (role.toUpperCase().equals("NONE")) {
            ps = c.prepareStatement("set role none");
        } else {
            ps = c.prepareStatement("set role ?");
            ps.setString(1, role);
        }

        ps.execute();
        ps.close();
    }

    /**
     * Perform a bulk grant or revoke action for grantee
     */
    private void doGrantRevoke(int action,
                               String grantor,
                               String[] actionStrings,
                               String grantee,
                               String[] warningExpected)
            throws SQLException {
        Connection c = openUserConnection(grantor);
        Statement s = c.createStatement();

        for (int i=0; i < actionStrings.length; i++) {
            s.execute(
                (action == GRANT ? "grant " : "revoke ") +
                actionStrings[i] +
                (action == GRANT ? " to " : " from ") +
                grantee +
                (action == REVOKE && actionStrings[i].startsWith
                 ("execute") ? " restrict" : ""));

            if (warningExpected[i] != null) {
                assertSQLState(warningExpected[i], s.getWarnings());
            }
        }

        s.close();
        c.close();
    }


    /**
     * Perform a bulk grant or revoke action for grantee
     */
    private void doGrantRevoke(int action,
                               String grantor,
                               String[] actionStrings,
                               String grantee)
            throws SQLException {
        String[] warns = new String[actionStrings.length];
        doGrantRevoke(action, grantor, actionStrings, grantee, warns);
    }

    /**
     * Perform a bulk grant or revoke action for grantee
     */
    private void doGrantRevoke(int action,
                               String grantor,
                               String actionString,
                               String grantee,
                               String warningExpected) throws SQLException {
        doGrantRevoke(action, grantor, new String[] {actionString}, grantee,
                      new String[]{warningExpected});
    }

    /**
     * Perform a bulk grant or revoke action for grantee
     */
    private void doGrantRevoke(int action,
                               String grantor,
                               String actionString,
                               String grantee) throws SQLException {
        doGrantRevoke(action, grantor, new String[] {actionString}, grantee);
    }


    private String CNFUser2user(String CNFUser) {
        for (int i = 0; i < users.length; i++) {
            if (JDBC.identifierToCNF(users[i]).equals(CNFUser)) {
                return users[i];
            }
        }
        fail("test error");
        return null;
    }

    private void assertSQLState(String[] ok_states, SQLException e) {
        String state = e.getSQLState();
        boolean found = false;

        for (int i = 0; i < ok_states.length; i++) {
            if (ok_states[i].equals(state)) {
                found = true;
            }
        }

        if (!found) {
            StringBuffer b = new StringBuffer();
            b.append("Exception ");
            b.append(state);
            b.append(" found, one of ");
            for (int i =  0; i < ok_states.length; i++) {
                b.append(ok_states[i]);
                if (i !=  ok_states.length - 1) {
                    b.append('|');
                }
            }
            b.append(" expected");
            fail(b.toString());
        }
    }

    public static void calledNested()
            throws SQLException
    {
        Connection c = null;

        try {
            c = DriverManager.getConnection("jdbc:default:connection");
            Statement cStmt = c.createStatement();

            // CREATE TABLE
            cStmt.executeUpdate
                ("create table t(role varchar(128) default current_role)");
            cStmt.executeUpdate("insert into t values default");
            ResultSet rs = cStmt.executeQuery("select * from t");
            JDBC.assertSingleValueResultSet(rs, "\"H\"");
            rs.close();
            cStmt.executeUpdate("drop table t");

            // ALTER TABLE
            cStmt.executeUpdate("create table t(i int)");
            cStmt.executeUpdate("insert into t values 1");
            cStmt.executeUpdate
                ("alter table t " +
                 "add column role varchar(10) default current_role");
            rs = cStmt.executeQuery("select * from t");
            JDBC.assertFullResultSet(rs, new String[][]{{"1",  "\"H\""}});
            rs.close();
            cStmt.executeUpdate("drop table t");
            cStmt.close();
        } finally {
            if (c != null) {
                try {
                    c.close();
                } catch (Exception e) {
                }
            }
        }
    }


    public static String getCurrentRole()
            throws SQLException
    {
        Connection c = null;

        try {
            c = DriverManager.getConnection("jdbc:default:connection");
            Statement cStmt = c.createStatement();

            ResultSet rs = cStmt.executeQuery("values current_role");
            rs.next();
            String result = rs.getString(1);
            rs.close();
            cStmt.close();
            return result;
        } finally {
            if (c != null) {
                try {
                    c.close();
                } catch (Exception e) {
                }
            }
        }
    }
}