| /* |
| |
| Derby - Class org.apache.derbyTesting.functionTests.tests.upgradeTests.Changes10_9 |
| |
| Licensed to the Apache Software Foundation (ASF) under one or more |
| contributor license agreements. See the NOTICE file distributed with |
| this work for additional information regarding copyright ownership. |
| The ASF licenses this file to You under the Apache License, Version 2.0 |
| (the "License"); you may not use this file except in compliance with |
| the License. You may obtain a copy of the License at |
| |
| http://www.apache.org/licenses/LICENSE-2.0 |
| |
| Unless required by applicable law or agreed to in writing, software |
| distributed under the License is distributed on an "AS IS" BASIS, |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| See the License for the specific language governing permissions and |
| limitations under the License. |
| |
| */ |
| package org.apache.derbyTesting.functionTests.tests.upgradeTests; |
| |
| import java.io.File; |
| import java.net.MalformedURLException; |
| import java.net.URL; |
| import java.sql.CallableStatement; |
| import java.sql.Connection; |
| import java.sql.PreparedStatement; |
| import java.sql.ResultSet; |
| import java.sql.SQLException; |
| import java.sql.Statement; |
| import java.util.ArrayList; |
| import java.util.List; |
| import javax.sql.DataSource; |
| import junit.framework.Test; |
| import org.apache.derbyTesting.functionTests.tests.upgradeTests.helpers.DisposableIndexStatistics; |
| import org.apache.derbyTesting.junit.BaseTestSuite; |
| import org.apache.derbyTesting.junit.IndexStatsUtil; |
| import org.apache.derbyTesting.junit.JDBC; |
| import org.apache.derbyTesting.junit.JDBCDataSource; |
| import org.apache.derbyTesting.junit.SupportFilesSetup; |
| import org.apache.derbyTesting.junit.TestConfiguration; |
| |
| |
| /** |
| * Upgrade test cases for 10.9. |
| */ |
| public class Changes10_9 extends UpgradeChange |
| { |
| /////////////////////////////////////////////////////////////////////////////////// |
| // |
| // CONSTANTS |
| // |
| /////////////////////////////////////////////////////////////////////////////////// |
| |
| private static final String UPGRADE_REQUIRED = "XCL47"; |
| private static final String INVALID_PROVIDER_CHANGE = "XCY05"; |
| |
| /////////////////////////////////////////////////////////////////////////////////// |
| // |
| // STATE |
| // |
| /////////////////////////////////////////////////////////////////////////////////// |
| |
| /////////////////////////////////////////////////////////////////////////////////// |
| // |
| // CONSTRUCTOR |
| // |
| /////////////////////////////////////////////////////////////////////////////////// |
| |
| public Changes10_9(String name) |
| { |
| super(name); |
| initPattern(); |
| } |
| |
| /////////////////////////////////////////////////////////////////////////////////// |
| // |
| // JUnit BEHAVIOR |
| // |
| /////////////////////////////////////////////////////////////////////////////////// |
| |
| private static final String[] SUPPORT_FILES_SOURCE = |
| { |
| "functionTests/tests/lang/dcl_java.jar", |
| "functionTests/tests/lang/dcl_emc1.jar", |
| "functionTests/tests/lang/dcl_emc2.jar", |
| }; |
| |
| |
| /** |
| * Return the suite of tests to test the changes made in 10.7. |
| * @param phase an integer that indicates the current phase in |
| * the upgrade test. |
| * @return the test suite created. |
| */ |
| public static Test suite(int phase) { |
| BaseTestSuite suite = new BaseTestSuite("Upgrade test for 10.9"); |
| |
| suite.addTestSuite(Changes10_9.class); |
| |
| return new SupportFilesSetup( |
| (Test)suite, SUPPORT_FILES_SOURCE); |
| } |
| |
| /////////////////////////////////////////////////////////////////////////////////// |
| // |
| // TESTS |
| // |
| /////////////////////////////////////////////////////////////////////////////////// |
| |
| /** |
| * Make sure that the drop statistics procedure only appears after |
| * hard-upgrade. |
| */ |
| public void testDropStatisticsProc() throws Exception |
| { |
| Statement s = createStatement(); |
| |
| switch ( getPhase() ) |
| { |
| case PH_CREATE: // create with old version |
| s.execute("CREATE TABLE dropStatsT1 (c11 int, c12 int) "); |
| vetProcs(s, "call syscs_util.syscs_drop_statistics( 'APP', 'DROPSTATST1', null )", false); |
| break; |
| |
| case PH_SOFT_UPGRADE: // boot with new version and soft-upgrade |
| vetProcs(s, "call syscs_util.syscs_drop_statistics( 'APP', 'DROPSTATST1', null )", false); |
| break; |
| |
| case PH_POST_SOFT_UPGRADE: // soft-downgrade: boot with old version after soft-upgrade |
| vetProcs(s, "call syscs_util.syscs_drop_statistics( 'APP', 'DROPSTATST1', null )", false); |
| break; |
| |
| case PH_HARD_UPGRADE: // boot with new version and hard-upgrade |
| vetProcs(s, "call syscs_util.syscs_drop_statistics( 'APP', 'DROPSTATST1', null )", true); |
| s.execute("DROP TABLE dropStatsT1"); |
| break; |
| } |
| |
| s.close(); |
| |
| } |
| /** |
| * Make sure that the catalogs and procedures for NATIVE authentication |
| * only appear after hard-upgrade. |
| */ |
| public void testNativeAuthentication() throws Exception |
| { |
| Statement s = createStatement(); |
| |
| switch ( getPhase() ) |
| { |
| case PH_CREATE: // create with old version |
| vetSYSUSERS( s, false ); |
| vetNativeProcs( s, false ); |
| break; |
| |
| case PH_SOFT_UPGRADE: // boot with new version and soft-upgrade |
| vetSYSUSERS( s, false ); |
| vetNativeProcs( s, false ); |
| break; |
| |
| case PH_POST_SOFT_UPGRADE: // soft-downgrade: boot with old version after soft-upgrade |
| vetSYSUSERS( s, false ); |
| vetNativeProcs( s, false ); |
| break; |
| |
| case PH_HARD_UPGRADE: // boot with new version and hard-upgrade |
| vetSYSUSERS( s, true ); |
| vetNativeProcs( s, true ); |
| break; |
| } |
| |
| s.close(); |
| } |
| private void vetProcs( Statement s, String procCall, boolean shouldExist ) throws Exception |
| { |
| try { |
| s.execute( procCall ); |
| |
| if ( !shouldExist ) |
| { |
| fail( "syscs_util.syscs_create_user should not exist." ); |
| } |
| } catch (SQLException se ) |
| { |
| if ( shouldExist ) |
| { |
| assertSQLState( "4251K", se ); |
| } |
| else |
| { |
| assertSQLState( "42Y03", se ); |
| } |
| } |
| } |
| private void vetSYSUSERS( Statement s, boolean shouldExist ) throws Exception |
| { |
| ResultSet rs = s.executeQuery( "select count(*) from sys.systables where tablename = 'SYSUSERS'" ); |
| rs.next(); |
| |
| int expectedValue = shouldExist ? 1 : 0; |
| |
| assertEquals( expectedValue, rs.getInt( 1 ) ); |
| |
| rs.close(); |
| } |
| private void vetNativeProcs( Statement s, boolean shouldExist ) throws Exception |
| { |
| // make sure that an authentication algorithm has been set |
| String defaultDigestAlgorithm = pushAuthenticationAlgorithm( s ); |
| vetProcs(s, "call syscs_util.syscs_create_user( 'FRED', 'fredpassword' )", shouldExist); |
| // restore the authentication algorithm if we changed it |
| popAuthenticationAlgorithm( s, defaultDigestAlgorithm ); |
| } |
| private String pushAuthenticationAlgorithm( Statement s ) throws Exception |
| { |
| // make sure that an authentication algorithm has been set. |
| // otherwise, we won't be able to create NATIVE users. |
| String defaultDigestAlgorithm = getDatabaseProperty( s, "derby.authentication.builtin.algorithm" ); |
| if ( defaultDigestAlgorithm == null ) |
| { |
| setDatabaseProperty( s, "derby.authentication.builtin.algorithm", "SHA-1" ); |
| } |
| |
| return defaultDigestAlgorithm; |
| } |
| private void popAuthenticationAlgorithm( Statement s, String defaultDigestAlgorithm ) throws Exception |
| { |
| // restore the authentication algorithm if we changed it |
| if ( defaultDigestAlgorithm == null ) |
| { |
| setDatabaseProperty( s, "derby.authentication.builtin.algorithm", null ); |
| } |
| } |
| private void setDatabaseProperty( Statement s, String key, String value ) |
| throws Exception |
| { |
| if ( value == null ) { value = "cast ( null as varchar( 32672 ) )"; } |
| else { value = "'" + value + "'"; } |
| String command = "call syscs_util.syscs_set_database_property( '" + key + "', " + value + " )"; |
| |
| s.execute( command ); |
| } |
| private String getDatabaseProperty( Statement s, String key ) |
| throws Exception |
| { |
| ResultSet rs = s.executeQuery( "values( syscs_util.syscs_get_database_property( '" + key + "' ) )" ); |
| |
| try { |
| rs.next(); |
| return rs.getString( 1 ); |
| } |
| finally |
| { |
| rs.close(); |
| } |
| } |
| |
| /** |
| * Make sure that NATIVE LOCAL authentication can't be turned on |
| * before hard-upgrade. |
| */ |
| public void testNativeLocalAuthentication() throws Exception |
| { |
| Statement s = createStatement(); |
| |
| switch ( getPhase() ) |
| { |
| case PH_CREATE: // create with old version |
| case PH_POST_SOFT_UPGRADE: // soft-downgrade: boot with old version after soft-upgrade |
| |
| // |
| // It's possible (although very unlikely) that someone could set the |
| // authentication provider to be NATIVE::LOCAL in an old database |
| // just before upgrading. If they do this, they will get an error at |
| // soft-upgrade time and they will have to back off to the old |
| // derby version in order to unset the authentication provider. |
| // |
| setDatabaseProperty( s, "derby.authentication.provider", "NATIVE::LOCAL" ); |
| setDatabaseProperty( s, "derby.authentication.provider", null ); |
| break; |
| |
| case PH_SOFT_UPGRADE: // boot with new version and soft-upgrade |
| setDatabaseProperty( s, "derby.authentication.provider", "com.acme.AcmeAuthenticator" ); |
| assertStatementError |
| ( |
| UPGRADE_REQUIRED, s, |
| "call syscs_util.syscs_set_database_property( 'derby.authentication.provider', 'NATIVE::LOCAL' )" |
| ); |
| setDatabaseProperty( s, "derby.authentication.provider", null ); |
| break; |
| |
| case PH_HARD_UPGRADE: // boot with new version and hard-upgrade |
| // |
| // Can't actually turn on NATIVE LOCAL authentication in the upgrade tests because, once turned on, |
| // you can't turn it off and that would mess up later tests. |
| // |
| break; |
| } |
| |
| s.close(); |
| } |
| |
| /** |
| * Make sure builtin authentication doesn't use a hash scheme that's not |
| * supported by the old version until the database has been hard upgraded. |
| * See DERBY-4483 and DERBY-5539. |
| */ |
| public void testBuiltinAuthenticationWithConfigurableHash() |
| throws SQLException { |
| |
| // This test needs to enable authentication, which is not supported |
| // in the default database for the upgrade tests, so roll our own. |
| DataSource ds = JDBCDataSource.getDataSourceLogical("BUILTIN_10_9"); |
| |
| // Add create=true or upgrade=true, as appropriate, since we don't |
| // get this for free when we don't use the default database. |
| if (getPhase() == PH_CREATE) { |
| JDBCDataSource.setBeanProperty(ds, "createDatabase", "create"); |
| } else if (getPhase() == PH_HARD_UPGRADE) { |
| JDBCDataSource.setBeanProperty( |
| ds, "connectionAttributes", "upgrade=true"); |
| } |
| |
| // Connect as database owner, possibly creating or upgrading the |
| // database. |
| Connection c = ds.getConnection("dbo", "the boss"); |
| |
| // Let's first verify that all the users can connect after the changes |
| // in the previous phase. Would fail for instance in post soft upgrade |
| // if soft upgrade saved passwords using the new scheme. |
| verifyCanConnect(ds); |
| |
| CallableStatement setProp = c.prepareCall( |
| "call syscs_util.syscs_set_database_property(?, ?)"); |
| |
| if (getPhase() == PH_CREATE) { |
| // The database is being created. Make sure that builtin |
| // authentication is enabled. |
| setProp.setString(1, "derby.connection.requireAuthentication"); |
| setProp.setString(2, "true"); |
| setProp.execute(); |
| |
| setProp.setString(1, "derby.authentication.provider"); |
| setProp.setString(2, "BUILTIN"); |
| setProp.execute(); |
| |
| // Set the length of the random salt to 0 to ensure that the |
| // hashed token doesn't vary between test runs. |
| setProp.setString(1, "derby.authentication.builtin.saltLength"); |
| setProp.setInt(2, 0); |
| setProp.execute(); |
| } |
| |
| // Set (or reset) passwords for all users. |
| setPasswords(setProp); |
| setProp.close(); |
| |
| // We should still be able to connect. |
| verifyCanConnect(ds); |
| |
| // Check that the passwords are stored using the expected scheme (new |
| // configurable hash scheme in hard upgrade, old scheme otherwise). |
| verifyPasswords(c); |
| |
| c.close(); |
| |
| // The framework doesn't know how to shutdown a database using |
| // authentication, so do it manually as database owner here. |
| JDBCDataSource.setBeanProperty(ds, "user", "dbo"); |
| JDBCDataSource.setBeanProperty(ds, "password", "the boss"); |
| JDBCDataSource.shutdownDatabase(ds); |
| } |
| |
| /** |
| * Information about users for the test of builtin authentication with |
| * configurable hash algorithm. Two-dimensional array of strings where |
| * each row contains (1) a user name, (2) a password, (3) the name of a |
| * digest algorithm with which the password should be hashed, (4) the |
| * hashed password when the old scheme is used, (5) the hashed password |
| * when the new, configurable hash scheme is used in databases that |
| * don't support the key-stretching extension (DERBY-5539), and (6) the |
| * hashed password when configurable hash with key stretching is used. |
| */ |
| private static final String[][] USERS = { |
| { "dbo", "the boss", null, |
| "3b6071d99b1d48ab732e75a8de701b6c77632db65898", |
| "3b6071d99b1d48ab732e75a8de701b6c77632db65898", |
| "3b6071d99b1d48ab732e75a8de701b6c77632db65898", |
| }, |
| { "pat", "postman", "MD5", |
| "3b609129e181a7f7527697235c8aead65c461a0257f3", |
| "3b61aaca567ed43d1ba2e6402cbf1a723407:MD5", |
| "3b624f4b0d7f3d2330c1db98a2000c62b5cd::1000:MD5", |
| }, |
| { "sam", "fireman", "SHA-1", |
| "3b609e5173cfa03620061518adc92f2a58c7b15cf04f", |
| "3b6197160362c0122fcd7a63a9da58fd0781140901fb:SHA-1", |
| "3b62a2d88ffac5332219116ab53e29dd3b9e1222e990::1000:SHA-1", |
| }, |
| }; |
| |
| /** |
| * Set the passwords for all users specified in {@code USERS}. |
| * |
| * @param cs a callable statement that sets database properties |
| */ |
| private void setPasswords(CallableStatement cs) throws SQLException { |
| for (int i = 0; i < USERS.length; i++) { |
| // Use the specified algorithm, if possible. (Will be ignored if |
| // the data dictionary doesn't support the new scheme.) |
| cs.setString(1, Changes10_6.HASH_ALGORITHM_PROPERTY); |
| cs.setString(2, USERS[i][2]); |
| cs.execute(); |
| // Set the password. |
| cs.setString(1, "derby.user." + USERS[i][0]); |
| cs.setString(2, USERS[i][1]); |
| cs.execute(); |
| } |
| } |
| |
| /** |
| * Verify that all passwords for the users in {@code USERS} are stored |
| * as expected. Raise an assert failure on mismatch. |
| * |
| * @param c a connection to the database |
| */ |
| private void verifyPasswords(Connection c) |
| throws SQLException { |
| int pwIdx; |
| if (getPhase() == PH_HARD_UPGRADE) { |
| // Expect configurable hash scheme with key stretching in fully |
| // upgraded databases. |
| pwIdx = 5; |
| } else if (oldAtLeast(10, 6)) { |
| // Databases whose dictionary is at least version 10.6 support |
| // configurable hash without key stretching. |
| pwIdx = 4; |
| } else { |
| // Older databases only support the old scheme based on SHA-1. |
| pwIdx = 3; |
| } |
| PreparedStatement ps = c.prepareStatement( |
| "values syscs_util.syscs_get_database_property(?)"); |
| for (int i = 0; i < USERS.length; i++) { |
| String expectedToken = USERS[i][pwIdx]; |
| ps.setString(1, "derby.user." + USERS[i][0]); |
| JDBC.assertSingleValueResultSet(ps.executeQuery(), expectedToken); |
| } |
| ps.close(); |
| } |
| |
| /** |
| * Verify that all users specified in {@code USERS} can connect to the |
| * database. |
| * |
| * @param ds a data source for connecting to the database |
| * @throws SQLException if one of the users cannot connect to the database |
| */ |
| private void verifyCanConnect(DataSource ds) throws SQLException { |
| for (int i = 0; i < USERS.length; i++) { |
| Connection c = ds.getConnection(USERS[i][0], USERS[i][1]); |
| c.close(); |
| } |
| } |
| |
| |
| /** |
| * For 10.9 and later storage of jar files changed. DERBY-5357. |
| */ |
| public void testJarStorage() throws Exception |
| { |
| Statement s = createStatement(); |
| |
| switch (getPhase()) { |
| case PH_CREATE: // create with old version |
| createSchema("EMC"); |
| createSchema("FOO"); |
| |
| s.executeUpdate( |
| "create procedure EMC.ADDCONTACT(id INT, e_mail VARCHAR(30)) " + |
| "MODIFIES SQL DATA " + |
| "external name " + |
| "'org.apache.derbyTesting.databaseclassloader.emc.addContact'" + |
| " language java parameter style java"); |
| s.executeUpdate( |
| "create table EMC.CONTACTS " + |
| " (id int, e_mail varchar(30))"); |
| |
| installJar("dcl_emc1.jar", "EMC.MAIL_APP"); |
| installJar("dcl_java.jar", "EMC.MY_JAVA"); |
| installJar("dcl_emc2.jar", "FOO.BAR"); |
| |
| setDBClasspath("EMC.MAIL_APP"); |
| tryCall(); |
| setDBClasspath(null); |
| |
| break; |
| |
| case PH_SOFT_UPGRADE: |
| // boot with new version and soft-upgrade |
| |
| // DERBY-6505: Take a backup of the database. Backing up the |
| // jar storage takes a different path in soft upgrade, so make |
| // sure that path is exercised. |
| String backupdir = |
| SupportFilesSetup.getReadWriteFileName("d6505-backup"); |
| PreparedStatement backupStmt = |
| prepareStatement("call syscs_util.syscs_backup_database(?)"); |
| backupStmt.setString(1, backupdir); |
| backupStmt.execute(); |
| |
| // fallthrough... run the same tests as in post soft upgrade. |
| case PH_POST_SOFT_UPGRADE: |
| // soft-downgrade: boot with old version after soft-upgrade |
| |
| setDBClasspath("EMC.MAIL_APP"); |
| tryCall(); |
| setDBClasspath(null); |
| |
| // if we can do this, it hasn't moved already: |
| replaceJar("dcl_emc1.jar", "EMC.MAIL_APP"); |
| |
| setDBClasspath("EMC.MAIL_APP"); |
| tryCall(); |
| setDBClasspath(null); |
| |
| break; |
| |
| case PH_HARD_UPGRADE: // boot with new version and hard-upgrade |
| |
| setDBClasspath("EMC.MAIL_APP"); |
| tryCall(); |
| setDBClasspath(null); |
| |
| installJar("dcl_emc1.jar", "FOO.\"BAR/..\\../\""); |
| |
| verifyNewLocations(4); |
| |
| removeJar("EMC.MAIL_APP"); |
| installJar("dcl_emc1.jar", "EMC.MAIL_APP"); |
| |
| setDBClasspath("EMC.MAIL_APP"); |
| tryCall(); |
| setDBClasspath(null); |
| |
| // finally, check that all the rest are also here |
| replaceJar("dcl_java.jar", "EMC.MY_JAVA"); |
| replaceJar("dcl_emc2.jar", "FOO.BAR"); |
| replaceJar("dcl_emc1.jar", "FOO.\"BAR/..\\../\""); |
| |
| // clean up |
| removeJar("EMC.MY_JAVA"); |
| removeJar("FOO.BAR"); |
| removeJar("FOO.\"BAR/..\\../\""); |
| removeJar("EMC.MAIL_APP"); |
| s.executeUpdate("drop table EMC.CONTACTS"); |
| s.executeUpdate("drop procedure EMC.ADDCONTACT"); |
| s.executeUpdate("drop schema FOO restrict"); |
| s.executeUpdate("drop schema EMC restrict"); |
| |
| break; |
| } |
| |
| s.close(); |
| } |
| |
| private void createSchema(String name) throws SQLException { |
| Statement s = createStatement(); |
| s.executeUpdate("create schema " + name); |
| s.close(); |
| } |
| |
| private void installJar(String resource, String jarName) |
| throws SQLException, MalformedURLException { |
| |
| URL jar = SupportFilesSetup.getReadOnlyURL(resource); |
| |
| CallableStatement cs = prepareCall("CALL SQLJ.INSTALL_JAR(?, ?, 0)"); |
| cs.setString(1, jar.toExternalForm()); |
| cs.setString(2, jarName); |
| cs.executeUpdate(); |
| cs.close(); |
| } |
| |
| private void replaceJar(String resource, String jarName) |
| throws SQLException, MalformedURLException { |
| |
| URL jar = SupportFilesSetup.getReadOnlyURL(resource); |
| CallableStatement cs = prepareCall("CALL SQLJ.REPLACE_JAR(?, ?)"); |
| cs.setString(1, jar.toExternalForm()); |
| cs.setString(2, jarName); |
| cs.executeUpdate(); |
| cs.close(); |
| } |
| |
| private void removeJar(String jarName) throws SQLException { |
| CallableStatement cs = prepareCall("CALL SQLJ.REMOVE_JAR(?, 0)"); |
| cs.setString(1, jarName); |
| cs.executeUpdate(); |
| cs.close(); |
| } |
| |
| private void setDBClasspath(String cp) throws SQLException { |
| CallableStatement cs = prepareCall( |
| "CALL SYSCS_UTIL.SYSCS_SET_DATABASE_PROPERTY(" + |
| "'derby.database.classpath', ?)"); |
| |
| cs.setString(1, cp); |
| cs.executeUpdate(); |
| cs.close(); |
| } |
| |
| private void tryCall() throws SQLException { |
| if (JDBC.vmSupportsJSR169()) { |
| return; // skip, EMC uses DriverManager |
| } |
| |
| CallableStatement cs = prepareCall("CALL EMC.ADDCONTACT(?, ?)"); |
| cs.setInt(1, 0); |
| cs.setString(2, "now@classpathchange.com"); |
| cs.executeUpdate(); |
| cs.close(); |
| } |
| |
| private void verifyNewLocations(int noOfObjects) |
| throws SQLException { |
| TestConfiguration tc = TestConfiguration.getCurrent(); |
| String dbPath = tc.getPhysicalDatabaseName(tc.getDefaultDatabaseName()); |
| String jarDirName = |
| "system" + File.separator + dbPath + File.separator + "jar"; |
| File jarDir = new File(jarDirName); |
| |
| assertTrue(jarDir.isDirectory()); |
| |
| File[] contents = jarDir.listFiles(); |
| |
| // <db>/jar should now contain this no of files, none of which are |
| // directories |
| assertEquals(noOfObjects, contents.length); |
| |
| // assert that all the old style directories are gone |
| for (int i=0; i < contents.length; i++) { |
| File f = contents[i]; |
| assertTrue(f.isFile()); |
| assertFileNameShape(f.getName()); |
| } |
| } |
| |
| |
| /** |
| * Regexp pattern to match the file name of a jar file stored in the |
| * database (version >= 10.9). |
| */ |
| private Goal[] pattern; |
| |
| /** |
| * Initialize a pattern corresponding to: |
| * <p/> |
| * <Derby uuid string>[.]jar[.]G[0-9]+ |
| * <p/> |
| * where: |
| * <p/> |
| * <Derby uuid string> has the form |
| * hhhhhhhh-hhhh-hhhh-hhhh-hhhhhhhhhhhh |
| * <p/> |
| * where <em>h</em> id a lower case hex digit. |
| */ |
| private void initPattern() { |
| List<Goal> l = new ArrayList<Goal>(100); |
| // The UUID format is determined by |
| // org.apache.derby.impl.services.uuid.BasicUUID#toString |
| |
| for (int i=0; i < 8; i++) { |
| l.add(new CharRange(new char[][]{{'0','9'},{'a','f'}})); |
| } |
| |
| l.add(new SingleChar('-')); |
| |
| for (int j = 0; j < 3; j++) { |
| for (int i=0; i < 4; i++) { |
| l.add(new CharRange(new char[][]{{'0','9'},{'a','f'}})); |
| } |
| |
| l.add(new SingleChar('-')); |
| } |
| |
| for (int i=0; i < 12; i++) { |
| l.add(new CharRange(new char[][]{{'0','9'},{'a','f'}})); |
| } |
| |
| l.add(new SingleChar('.')); |
| l.add(new SingleChar('j')); |
| l.add(new SingleChar('a')); |
| l.add(new SingleChar('r')); |
| l.add(new SingleChar('.')); |
| l.add(new SingleChar('G')); |
| l.add(new CharRange(new char[][]{{'0','9'}}, Goal.REPEAT)); |
| |
| this.pattern = l.toArray(new Goal[l.size()]); |
| } |
| |
| /** |
| * assert that fName has the expected shape of a jar file |
| * in the database (version >= 10.9). |
| */ |
| private void assertFileNameShape(String fName) { |
| assertTrue(matches(fName, pattern)); |
| } |
| |
| /** |
| * Poor man's regexp matcher: can match patterns of type below, where |
| * start "^" and end "$" is implied: must match whole string. |
| * <p/> |
| * reg.exp: ( '[' <fromchar>-<tochar> ] '+'? ']' | |
| * <char> '+'? )* |
| */ |
| private boolean matches(String fName, Goal[] pattern) { |
| int patIdx = 0; |
| for (int i = 0; i < fName.length(); i++) { |
| Goal p = pattern[patIdx]; |
| char c = fName.charAt(i); |
| |
| if (p.matches(c)) { |
| if (!p.isRepeatable()) { |
| patIdx++; |
| } |
| p.setFoundOnce(); |
| continue; |
| } |
| |
| // Goal did not match: if we have a repeatable goal and we already |
| // found one occurence it's ok, to step on to next goal in pattern |
| // and see it that matches. |
| patIdx++; |
| if (p.matches(c)) { |
| if (!p.isRepeatable()) { |
| patIdx++; |
| } |
| p.setFoundOnce(); |
| continue; |
| } |
| |
| return false; |
| |
| } |
| |
| return patIdx >= (pattern.length - 1); // exact match |
| } |
| |
| abstract class Goal { |
| public abstract boolean matches(char c); |
| |
| public final static int REPEAT = 0; // optional goal property |
| int option = -1; |
| boolean foundOnce = false; |
| |
| public boolean isRepeatable () { |
| return option == REPEAT; |
| } |
| |
| public void setFoundOnce() { |
| this.foundOnce = true; |
| } |
| |
| public boolean foundOnce () { |
| return this.foundOnce; |
| } |
| } |
| |
| private class CharRange extends Goal { |
| private char[][] ranges; |
| |
| public CharRange(char[][]ranges) { |
| this.ranges = (char[][])ranges.clone(); |
| } |
| |
| public CharRange(char[][]ranges, int option) { |
| this.ranges = (char[][])ranges.clone(); |
| this.option = option; |
| } |
| |
| public boolean matches(char c) { |
| for (int i = 0; i < ranges.length; i++) { |
| if (c >= ranges[i][0] && c <= ranges[i][1]) { |
| return true; |
| } |
| } |
| return false; |
| } |
| } |
| |
| private class SingleChar extends Goal { |
| private char c; |
| private int option = -1; |
| private boolean foundOnce = false; |
| |
| public SingleChar(char c) { |
| this.c = c; |
| } |
| |
| public SingleChar(char c, int option) { |
| this.c = c; |
| this.option = option; |
| } |
| public boolean matches(char c) { |
| return c == this.c; |
| } |
| } |
| |
| |
| /** |
| * Test the changes introduced to fix correctness problems with sequences. |
| */ |
| public void test_5493() throws Exception |
| { |
| Connection conn = getConnection(); |
| Statement s = createStatement(); |
| |
| switch ( getPhase() ) |
| { |
| case PH_CREATE: // create with old version |
| assertNull( getNewFunctionID( s ) ); |
| break; |
| |
| case PH_SOFT_UPGRADE: // boot with new version and soft-upgrade |
| assertNull( getNewFunctionID( s ) ); |
| break; |
| |
| case PH_POST_SOFT_UPGRADE: // soft-downgrade: boot with old version after soft-upgrade |
| assertNull( getNewFunctionID( s ) ); |
| break; |
| |
| case PH_HARD_UPGRADE: // boot with new version and hard-upgrade |
| assertNotNull( getNewFunctionID( s ) ); |
| break; |
| } |
| |
| s.close(); |
| } |
| private String getNewFunctionID( Statement s ) |
| throws Exception |
| { |
| ResultSet rs = null; |
| |
| try { |
| rs = s.executeQuery |
| ( "select aliasid from sys.sysaliases where alias = 'SYSCS_PEEK_AT_SEQUENCE'" ); |
| if ( !rs.next() ) { return null; } |
| else { return rs.getString( 1 ); } |
| } |
| finally |
| { |
| if ( rs != null ) { rs.close(); } |
| } |
| } |
| |
| /** |
| * Verifies that an orphaned statistics entry can be dropped by running the |
| * {@code SYSCS_DROP_STATISTICS} system procedure. |
| * <p> |
| * Relevant JIRAs: |
| * <ul> <li>DERBY-4115: Provide a way to drop statistics information</li> |
| * <li>DERBY-5681: When a foreign key constraint on a table is dropped, |
| * the associated statistics row for the conglomerate</li> |
| * </ul> |
| * <p> |
| * DERBY-5702(Creating a foreign key constraint does not automatically |
| * create a statistics row if foreign key constraint will share a |
| * backing index created for a primay key) is causing a problem for |
| * us to test the hanging statistics row with 10.4 and prior releases. |
| * Following test relies on having hanging statistics rows which should |
| * have been dropped when the constraint owing it was dropped. The test |
| * then goes ahead and uses the new drop statisitcs procedure to drop |
| * the hanging statistics rows. But because of DERBY-5702, when a |
| * constraint is added which will reuse an existing backing index, |
| * no statistics row is created for that constraint unless a user were |
| * to say use an update statistics stored procedure to create the |
| * statistics for that constraint. And later when that constraint is |
| * dropped, we will find that because of DERBY-5681, the statistics |
| * row never gets dropped. But update statistics stored procedure was |
| * not introduced up until 10.5 and because of that, we can't really |
| * test for hanging index created through constraints sharing the same |
| * backing index prior to 10.5 |
| */ |
| public void testDropOrphanedStatistics() |
| throws SQLException { |
| // Update statistics procedure SYSCS_UPDATE_STATISTICS is not available |
| // prior to 10.5 and hence we can't cause the hanging statistics to |
| // appear in order to test the drop statistics after hard upgrade |
| if (!oldAtLeast(10, 5)) return; |
| |
| // Helper object to obtain information about index statistics. |
| IndexStatsUtil stats = new IndexStatsUtil(openDefaultConnection()); |
| Statement s = createStatement(); |
| // The expected initial number of statistics entries in TEST_TAB_2. |
| final int expected = |
| DisposableIndexStatistics.hasDerby5681Bug(getOldVersion()) ? |
| 2 : 1; |
| |
| switch (getPhase()) |
| { |
| case PH_CREATE: |
| s.executeUpdate("CREATE TABLE TEST_TAB_1 (c11 int not null,"+ |
| "c12 int not null, c13 int)"); |
| s.executeUpdate("INSERT INTO TEST_TAB_1 VALUES(1,1,1),(2,2,2)"); |
| s.executeUpdate("ALTER TABLE TEST_TAB_1 "+ |
| "ADD CONSTRAINT TEST_TAB_1_PK_1 "+ |
| "PRIMARY KEY (c11)"); |
| //The statistics for primary key constraint has been added |
| stats.assertTableStats("TEST_TAB_1",1); |
| |
| s.executeUpdate("CREATE TABLE TEST_TAB_2 (c21 int not null)"); |
| s.executeUpdate("INSERT INTO TEST_TAB_2 VALUES(1),(2)"); |
| s.executeUpdate("ALTER TABLE TEST_TAB_2 "+ |
| "ADD CONSTRAINT TEST_TAB_2_PK_1 "+ |
| "PRIMARY KEY (c21)"); |
| stats.assertTableStats("TEST_TAB_2",1); |
| //DERBY-5702 Add a foreign key constraint and now we should find 2 rows |
| // of statistics for TEST_TAB_2 - 1 for primary key and other for |
| // foreign key constraint |
| s.executeUpdate("ALTER TABLE TEST_TAB_2 "+ |
| "ADD CONSTRAINT TEST_TAB_2_FK_1 "+ |
| "FOREIGN KEY(c21) REFERENCES TEST_TAB_1(c11)"); |
| //DERBY-5702 Like primary key earlier, adding foreign key constraint |
| // didn't automatically add a statistics row for it. Have to run update |
| // statistics manually to get a row added for it's stat |
| stats.assertTableStats("TEST_TAB_2",1); |
| //Need to do a compress table to create the statistics for foreign |
| // key constraint. Update statisitcs procedure is only available |
| // in 10.5 and upwards and hence can't use that procedure here |
| // since we are testing older releases too. |
| s.execute("CALL SYSCS_UTIL.SYSCS_UPDATE_STATISTICS('APP','TEST_TAB_2', null)"); |
| //s.execute("CALL SYSCS_UTIL.SYSCS_COMPRESS_TABLE('APP','TEST_TAB_2',1)"); |
| stats.assertTableStats("TEST_TAB_2",2); |
| s.executeUpdate("ALTER TABLE TEST_TAB_2 "+ |
| "DROP CONSTRAINT TEST_TAB_2_FK_1"); |
| //Dropping the foreign key constraint does not remove it's |
| // statistics row because of DERBY-5681. |
| stats.assertTableStats("TEST_TAB_2", expected); |
| assertStatementError("42Y03", s, |
| "CALL SYSCS_UTIL.SYSCS_DROP_STATISTICS('APP','TEST_TAB_2', null)"); |
| break; |
| |
| case PH_SOFT_UPGRADE: |
| case PH_POST_SOFT_UPGRADE: |
| assertStatementError("42Y03", s, |
| "CALL SYSCS_UTIL.SYSCS_DROP_STATISTICS('APP','TEST_TAB_2', null)"); |
| break; |
| |
| case PH_HARD_UPGRADE: |
| stats.assertTableStats("TEST_TAB_2", expected); |
| s.execute("CALL SYSCS_UTIL.SYSCS_DROP_STATISTICS('APP','TEST_TAB_2', null)"); |
| stats.assertNoStatsTable("TEST_TAB_2"); |
| s.execute("CALL SYSCS_UTIL.SYSCS_UPDATE_STATISTICS('APP','TEST_TAB_2', null)"); |
| stats.assertNoStatsTable("TEST_TAB_2"); |
| break; |
| |
| case PH_POST_HARD_UPGRADE: |
| //Make sure that the new procedure is still available |
| s.execute("CALL SYSCS_UTIL.SYSCS_DROP_STATISTICS('APP','TEST_TAB_2', null)"); |
| s.executeUpdate("DROP TABLE TEST_TAB_1"); |
| s.executeUpdate("DROP TABLE TEST_TAB_2"); |
| break; |
| } |
| } |
| |
| /** |
| * Verifies the behavior of the update statistics code when faced with |
| * "disposable statistics entries". |
| * <p> |
| * A disposable statistics entry is a row in SYS.SYSSTATISTICS that has |
| * been orphaned (see DERBY-5681) or it is on longer needed by the |
| * Derby optimizer (due to internal changes/improvements). |
| * <p> |
| * This test expects different things based on the phase: |
| * <dl> <dt>create</dt> |
| * <dd>- run statements that will cause disposable statistics |
| * entries to be created</dd> |
| * <dt>soft upgrade</dt> |
| * <dd>- run the new update statistics code, expecting it to leave the |
| * disposable statistics intact</dd> |
| * <dt>downgrade</dt> |
| * <dd>- verify that the relevant statistics are present</dd> |
| * <dt>hard upgrade</dt> |
| * <dd>- run the new update statistics code, expecting it to get rid |
| * of the disposable statistics</dd> |
| * </dl> |
| */ |
| public void testDisposableStatisticsExplicit() |
| throws SQLException { |
| // Don't run this test with versions prior to 10.5, since the |
| // required SYSCS_UPDATE_STATISTICS don't exist in older versions. |
| if (!oldAtLeast(10, 5)) { |
| return; |
| } |
| |
| final String TBL = "ISTAT_DISPOSABLE_STATS"; |
| String updateStatsSQL = "call syscs_util.syscs_update_statistics(" + |
| "'APP', ?, null)"; |
| DisposableIndexStatistics dis = new DisposableIndexStatistics( |
| getOldVersion(), getConnection(), TBL); |
| |
| switch (getPhase()) { |
| // create with old version |
| case PH_CREATE: |
| { |
| dis.createAndPopulateTables(); |
| // The expected number of statistics entries depends on the |
| // version of Derby used to create the database. Some older |
| // versions of Derby contained a bug and lacked optimizations, |
| // causing the number of statistics entries to increase. |
| // Just after creation and before any update statistics expect |
| // all stats to exist. |
| dis.assertStatsCount(false, false); |
| break; |
| } |
| // boot with new version and soft-upgrade |
| case PH_SOFT_UPGRADE: |
| { |
| PreparedStatement ps = prepareStatement(updateStatsSQL); |
| String[] tables = dis.getTableNames(); |
| // Update statistics on all relevant tables. |
| for (int i=0; i < tables.length; i++) { |
| ps.setString(1, tables[i]); |
| ps.executeUpdate(); |
| } |
| |
| // After soft upgrade and update statistics expect the |
| // orphaned index entry to be deleted, but the "unneeded |
| // disposable entries" are only deleted after hard upgrade. |
| dis.assertStatsCount(true, false); |
| break; |
| } |
| // soft-downgrade: boot with old version after soft-upgrade |
| case PH_POST_SOFT_UPGRADE: |
| { |
| |
| // expect no change in entries on downgrade, should be same |
| // as they were in soft upgrade. |
| dis.assertStatsCount(true, false); |
| break; |
| } |
| // boot with new version and hard-upgrade |
| case PH_HARD_UPGRADE: |
| { |
| // expect no change in entries on upgrade before update |
| // statistics. |
| dis.assertStatsCount(true, false); |
| PreparedStatement ps = prepareStatement(updateStatsSQL); |
| String[] tables = dis.getTableNames(); |
| for (int i=0; i < tables.length; i++) { |
| ps.setString(1, tables[i]); |
| ps.executeUpdate(); |
| } |
| // Confirm that we disposed of the statistics that were added |
| // due to a bug or simply not needed by Derby. |
| try { |
| dis.assertStatsCount(true, true); |
| } finally { |
| for (int i=0; i < tables.length; i++) { |
| dropTable(tables[i]); |
| } |
| } |
| commit(); |
| break; |
| } |
| } |
| } |
| } |