| /* |
| * Class org.apache.derbyTesting.functionTests.tests.lang.UpdateStatisticsTest |
| * |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| |
| package org.apache.derbyTesting.functionTests.tests.lang; |
| |
| import java.sql.CallableStatement; |
| import java.sql.Connection; |
| import java.sql.PreparedStatement; |
| import java.sql.ResultSet; |
| import java.sql.SQLException; |
| import java.sql.Statement; |
| import java.sql.Types; |
| import junit.framework.Test; |
| import org.apache.derbyTesting.junit.BaseJDBCTestCase; |
| import org.apache.derbyTesting.junit.DatabasePropertyTestSetup; |
| import org.apache.derbyTesting.junit.IndexStatsUtil; |
| import org.apache.derbyTesting.junit.JDBC; |
| import org.apache.derbyTesting.junit.TestConfiguration; |
| import org.apache.derbyTesting.junit.RuntimeStatisticsParser; |
| import org.apache.derbyTesting.junit.SQLUtilities; |
| |
| /** |
| * Tests for updating the statistics of one index or all the indexes on a |
| * table DERBY-269, DERBY-3788. |
| * Tests for dropping the statistics of one index or all the indexes on a |
| * table DERBY-4115. |
| */ |
| public class UpdateStatisticsTest extends BaseJDBCTestCase { |
| |
| public UpdateStatisticsTest(String name) { |
| super(name); |
| } |
| |
| public static Test suite() { |
| // Disable automatic index statistics generation. The generation will be |
| // triggered when preparing a statement and this will interfere |
| // with some of the asserts in testUpdateStatistics. |
| // With automatic generation enabled, testUpdateStatistics may |
| // fail intermittently due to timing, mostly when run |
| // with the client driver. |
| Test test = TestConfiguration.defaultSuite(UpdateStatisticsTest.class); |
| Test statsDisabled = DatabasePropertyTestSetup.singleProperty |
| ( test, "derby.storage.indexStats.auto", "false", true ); |
| return statsDisabled; |
| } |
| |
| /** |
| * Test that parser can work with column and index named STATISTICS and |
| * does not get confused with non-reserved keyword STATISTICS used by |
| * UPDATE and DROP STATISTICS syntax generated internally for |
| * SYSCS_DROP_STATISTICS and SYSCS_UPDATE_STATISTICS |
| */ |
| public void testIndexAndColumnNamedStatistics() throws SQLException { |
| String tbl = "T1"; |
| // Helper object to obtain information about index statistics. |
| IndexStatsUtil stats = new IndexStatsUtil(openDefaultConnection()); |
| Statement s = createStatement(); |
| |
| // Get the initial count of statistics in the database. |
| int initialStatsCount = stats.getStats().length; |
| |
| //Notice the name of one of the columns is STATISTICS |
| s.executeUpdate("CREATE TABLE t1 (c11 int, statistics int not null)"); |
| //Notice that the name of the index is STATISTICS which is same as |
| // one of the column names |
| s.executeUpdate("CREATE INDEX statistIcs ON t1(c11)"); |
| s.executeUpdate("INSERT INTO t1 VALUES(1,1)"); |
| stats.assertNoStatsTable(tbl); |
| //Drop the column named STATISTICS and make sure parser doesn't |
| // throw an error |
| s.executeUpdate("ALTER TABLE t1 DROP statistics"); |
| //Should still be able to call update/drop statistics on index |
| // STATISTICS |
| s.executeUpdate("CALL SYSCS_UTIL.SYSCS_UPDATE_STATISTICS('APP','T1','STATISTICS')"); |
| stats.assertTableStats(tbl, 1); |
| s.executeUpdate("CALL SYSCS_UTIL.SYSCS_DROP_STATISTICS('APP','T1','STATISTICS')"); |
| stats.assertNoStatsTable(tbl); |
| //Add the column named STATISTICS back |
| s.executeUpdate("ALTER TABLE t1 ADD COLUMN statistics int"); |
| stats.assertNoStatsTable(tbl); |
| //Update or drop statistics for index named STATISTICS. Note that there |
| // is also a column named STATISTICS in the table |
| s.executeUpdate("CALL SYSCS_UTIL.SYSCS_DROP_STATISTICS('APP','T1','STATISTICS')"); |
| stats.assertNoStatsTable(tbl); |
| s.executeUpdate("CALL SYSCS_UTIL.SYSCS_UPDATE_STATISTICS('APP','T1','STATISTICS')"); |
| stats.assertTableStats(tbl, 1); |
| s.executeUpdate("DROP TABLE t1"); |
| |
| // Check that we haven't created some other statistics as a side-effect. |
| assertEquals(initialStatsCount, stats.getStats().length); |
| } |
| |
| //DERBY-5750(Sending an empty string as table name to compress table |
| // procedure or empty string as index name to update statistics procedure |
| // makes the parser throw an exception.) |
| // |
| //No table name will result in exception since Derby doesn't know table |
| // whose statistics it needs to be update/drop |
| public void testStatisticsProcsWithEmptyParamsDerby5750() throws SQLException { |
| Statement s = createStatement(); |
| s.execute("create table DERBY5750_t1 (c11 int)"); |
| s.executeUpdate("CREATE INDEX DERBY5750_I1 ON DERBY5750_t1(c11)"); |
| //Following statements will give exceptions since there is no schema |
| // named empty string |
| assertStatementError( |
| "42Y07", s, |
| "call syscs_util.SYSCS_UPDATE_STATISTICS("+ |
| "'','DERBY5750_T1','DERBY5750_I1')"); |
| assertStatementError( |
| "42Y07", s, |
| "call syscs_util.SYSCS_DROP_STATISTICS("+ |
| "'','DERBY5750_T1','DERBY5750_I1')"); |
| assertStatementError( |
| "42Y07", s, |
| "call syscs_util.SYSCS_UPDATE_STATISTICS("+ |
| "'','','DERBY5750_I1')"); |
| assertStatementError( |
| "42Y07", s, |
| "call syscs_util.SYSCS_DROP_STATISTICS("+ |
| "'','','DERBY5750_I1')"); |
| |
| //null schema name will translate to current schema |
| s.execute("call syscs_util.SYSCS_UPDATE_STATISTICS("+ |
| "null,'DERBY5750_T1','DERBY5750_I1')"); |
| s.execute("call syscs_util.SYSCS_DROP_STATISTICS(" + |
| "null,'DERBY5750_T1','DERBY5750_I1')"); |
| |
| //Following statements will give exceptions since there is no table |
| // named empty string |
| assertStatementError( |
| "42X05", s, |
| "call syscs_util.SYSCS_UPDATE_STATISTICS("+ |
| "null,'','DERBY5750_I1')"); |
| assertStatementError( |
| "42X05", s, |
| "call syscs_util.SYSCS_DROP_STATISTICS("+ |
| "null,'','DERBY5750_I1')"); |
| assertStatementError( |
| "42X05", s, |
| "call syscs_util.SYSCS_UPDATE_STATISTICS("+ |
| "'APP','','DERBY5750_I1')"); |
| assertStatementError( |
| "42X05", s, |
| "call syscs_util.SYSCS_DROP_STATISTICS("+ |
| "'APP','','DERBY5750_I1')"); |
| |
| //Following statements will give exceptions since table name can't |
| // be null |
| assertStatementError( |
| "42X05", s, |
| "call syscs_util.SYSCS_UPDATE_STATISTICS("+ |
| "null,null,'DERBY5750_I1')"); |
| assertStatementError( |
| "42X05", s, |
| "call syscs_util.SYSCS_DROP_STATISTICS("+ |
| "null,null,'DERBY5750_I1')"); |
| assertStatementError( |
| "42X05", s, |
| "call syscs_util.SYSCS_UPDATE_STATISTICS("+ |
| "'APP',null,'DERBY5750_I1')"); |
| assertStatementError( |
| "42X05", s, |
| "call syscs_util.SYSCS_DROP_STATISTICS("+ |
| "'APP',null,'DERBY5750_I1')"); |
| |
| //Provide all the 3 params, schema, table and index name |
| s.execute("call syscs_util.SYSCS_UPDATE_STATISTICS("+ |
| "'APP','DERBY5750_T1','DERBY5750_I1')"); |
| s.execute("call syscs_util.SYSCS_DROP_STATISTICS("+ |
| "'APP','DERBY5750_T1','DERBY5750_I1')"); |
| |
| |
| //Following statements will give exceptions since there is no index |
| // named empty string |
| assertStatementError( |
| "42X65", s, |
| "call syscs_util.SYSCS_UPDATE_STATISTICS("+ |
| "'APP','DERBY5750_T1','')"); |
| assertStatementError( |
| "42X65", s, |
| "call syscs_util.SYSCS_DROP_STATISTICS("+ |
| "'APP','DERBY5750_T1','')"); |
| |
| s.execute("drop table DERBY5750_t1"); |
| } |
| |
| /** |
| * Test for update statistics |
| */ |
| public void testUpdateAndDropStatistics() throws SQLException { |
| String tbl1 = "T1"; |
| // Helper object to obtain information about index statistics. |
| IndexStatsUtil stats = new IndexStatsUtil(openDefaultConnection()); |
| Statement s = createStatement(); |
| |
| //Calls to update and drop statistics below should fail because |
| // table APP.T1 does not exist |
| dropTable("T1"); |
| assertStatementError("42Y55", s, |
| "CALL SYSCS_UTIL.SYSCS_DROP_STATISTICS('APP','T1',null)"); |
| assertStatementError("42Y55", s, |
| "CALL SYSCS_UTIL.SYSCS_UPDATE_STATISTICS('APP','T1',null)"); |
| |
| s.executeUpdate("CREATE TABLE t1 (c11 int, c12 varchar(128))"); |
| //following will pass now because we have created APP.T1 |
| s.execute("CALL SYSCS_UTIL.SYSCS_DROP_STATISTICS('APP','T1',null)"); |
| s.execute("CALL SYSCS_UTIL.SYSCS_UPDATE_STATISTICS('APP','T1',null)"); |
| |
| //following should fail because index I1 does not exist on table APP.T1 |
| assertStatementError("42X65", s, |
| "CALL SYSCS_UTIL.SYSCS_DROP_STATISTICS('APP','T1','I1')"); |
| assertStatementError("42X65", s, |
| "CALL SYSCS_UTIL.SYSCS_UPDATE_STATISTICS('APP','T1','I1')"); |
| |
| s.executeUpdate("CREATE INDEX i1 on t1(c12)"); |
| //following will pass now because we have created index I1 on APP.T1 |
| s.execute("CALL SYSCS_UTIL.SYSCS_DROP_STATISTICS('APP','T1','I1')"); |
| s.execute("CALL SYSCS_UTIL.SYSCS_UPDATE_STATISTICS('APP','T1','I1')"); |
| |
| //The following set of subtest will ensure that when an index is |
| //created on a table when there is no data in the table, then Derby |
| //will not generate a row for it in sysstatistics table. If the index |
| //is created after the table has data on it, there will be a row for |
| //it in sysstatistics table. In order to generate statistics for the |
| //first index, users can run the stored procedure |
| //SYSCS_UPDATE_STATISTICS |
| //So far the table t1 is empty and we have already created index I1 on |
| //it. Since three was no data in the table when index I1 was created, |
| //there will be no row in sysstatistics table |
| stats.assertNoStatsTable(tbl1); |
| //Now insert some data into t1 and then create a new index on the |
| //table. This will cause sysstatistics table to have one row for this |
| //new index. Old index will still not have a row for it in |
| //sysstatistics table |
| s.executeUpdate("INSERT INTO T1 VALUES(1,'a'),(2,'b'),(3,'c'),(4,'d')"); |
| s.executeUpdate("CREATE INDEX i2 ON t1(c11)"); |
| stats.assertTableStats(tbl1, 1); |
| //Drop the statistics on index I2 and then add it back by calling |
| // update statistics |
| s.execute("CALL SYSCS_UTIL.SYSCS_DROP_STATISTICS('APP','T1','I2')"); |
| //Since we dropped the only statistics that existed for table T1, there |
| // will no stats found at this point |
| stats.assertNoStatsTable(tbl1); |
| s.execute("CALL SYSCS_UTIL.SYSCS_UPDATE_STATISTICS('APP','T1','I2')"); |
| //The statistics for index I2 has been added back |
| stats.assertTableStats(tbl1, 1); |
| //Now update the statistics for the old index I1 using the new stored |
| //procedure. Doing this should add a row for it in sysstatistics table |
| s.execute("CALL SYSCS_UTIL.SYSCS_UPDATE_STATISTICS('APP','T1','I1')"); |
| stats.assertTableStats(tbl1, 2); |
| //Drop the statistics on index I1 and then add it back by calling |
| // update statistics |
| s.execute("CALL SYSCS_UTIL.SYSCS_DROP_STATISTICS('APP','T1','I1')"); |
| stats.assertTableStats(tbl1, 1); |
| s.execute("CALL SYSCS_UTIL.SYSCS_UPDATE_STATISTICS('APP','T1','I1')"); |
| stats.assertTableStats(tbl1, 2); |
| //Drop all the statistics on table T1 and then recreate all the |
| // statisitcs back again |
| s.execute("CALL SYSCS_UTIL.SYSCS_DROP_STATISTICS('APP','T1',null)"); |
| stats.assertNoStatsTable(tbl1); |
| s.execute("CALL SYSCS_UTIL.SYSCS_UPDATE_STATISTICS('APP','T1',null)"); |
| stats.assertTableStats(tbl1, 2); |
| //Dropping the index should get rid of it's statistics |
| s.executeUpdate("DROP INDEX I1"); |
| stats.assertTableStats(tbl1, 1); |
| |
| //calls to system procedure for update and drop statistics are |
| // internally converted into ALTER TABLE ... sql but that generated |
| // sql format is not available to end user to issue directly. Write a |
| // test case for these internal sql syntaxes |
| assertStatementError("42X01", s, |
| "ALTER TABLE APP.T1 ALL UPDATE STATISTICS"); |
| assertStatementError("42X01", s, |
| "ALTER TABLE APP.T1 UPDATE STATISTICS I1"); |
| assertStatementError("42X01", s, |
| "ALTER TABLE APP.T1 ALL DROP STATISTICS"); |
| assertStatementError("42X01", s, |
| "ALTER TABLE APP.T1 STATISTICS DROP I1"); |
| //cleanup |
| s.executeUpdate("DROP TABLE t1"); |
| |
| //Try update and drop statistics on global temporary table |
| s.executeUpdate("declare global temporary table SESSION.t1(c11 int, c12 int) on commit delete rows not logged"); |
| s.executeUpdate("insert into session.t1 values(11, 1)"); |
| //following should fail because update/drop statistics can't be issued |
| // on global temporary tables |
| assertStatementError("42995", s, |
| "CALL SYSCS_UTIL.SYSCS_UPDATE_STATISTICS('SESSION','T1',null)"); |
| assertStatementError("42995", s, |
| "CALL SYSCS_UTIL.SYSCS_DROP_STATISTICS('SESSION','T1',null)"); |
| |
| //Following test will show that updating the statistics will make a |
| //query pickup better index compare to prior to statistics availability. |
| // |
| //Check statistics update causes most efficient index usage |
| //Create a table with 2 non-unique indexes on 2 different columns. |
| //The indexes are created when the table is still empty and hence |
| //there are no statistics available for them in sys.sysstatistics. |
| //The table looks as follows |
| // create table t2(c21 int, c22 char(14), c23 char(200)) |
| // create index t2i1 on t2(c21) |
| // create index t2i2 on t2(c22) |
| //Load the data into the table and running following query will |
| //pickup index t2i1 on column c21 |
| // select * from t2 where c21=? and c22=? |
| //But once you make the statistics available for t2i2, the query |
| //will pickup index t2i2 on column c22 for the query above |
| // |
| //Start of test case for better index selection after statistics |
| //availability |
| s.executeUpdate("CREATE TABLE t2(c21 int, c22 char(14), c23 char(200))"); |
| //No statistics will be created for the 2 indexes because the table is |
| //empty |
| s.executeUpdate("CREATE INDEX t2i1 ON t2(c21)"); |
| s.executeUpdate("CREATE INDEX t2i2 ON t2(c22)"); |
| stats.assertNoStatsTable("T2"); |
| |
| PreparedStatement ps = prepareStatement("INSERT INTO T2 VALUES(?,?,?)"); |
| for (int i=0; i<1000; i++) { |
| ps.setInt(1, i%2); |
| ps.setString(2, "Tuple " +i); |
| ps.setString(3, "any value"); |
| ps.addBatch(); |
| } |
| ps.executeBatch(); |
| |
| s.execute("call SYSCS_UTIL.SYSCS_SET_RUNTIMESTATISTICS(1)"); |
| |
| //Executing the query below and looking at it's plan will show that |
| //we picked index T2I1 rather than T2I2 because there are no |
| //statistics available for T2I2 to show that it is a better index |
| ps = prepareStatement("SELECT * FROM t2 WHERE c21=? AND c22=?"); |
| ps.setInt(1, 0); |
| ps.setString(2, "Tuple 4"); |
| JDBC.assertDrainResults(ps.executeQuery()); |
| RuntimeStatisticsParser rtsp = SQLUtilities.getRuntimeStatisticsParser(s); |
| assertTrue(rtsp.usedSpecificIndexForIndexScan("T2","T2I1")); |
| |
| //Running the update statistics below will create statistics for T2I2 |
| s.execute("CALL SYSCS_UTIL.SYSCS_UPDATE_STATISTICS('APP','T2','T2I2')"); |
| stats.assertIndexStats("T2I2", 1); |
| |
| //Rerunning the query "SELECT * FROM t2 WHERE c21=? AND c22=?" and |
| //looking at it's plan will show that this time it picked up more |
| //efficient index which is T2I2. |
| JDBC.assertDrainResults(ps.executeQuery()); |
| rtsp = SQLUtilities.getRuntimeStatisticsParser(s); |
| assertTrue(rtsp.usedSpecificIndexForIndexScan("T2","T2I2")); |
| |
| //Drop statistics for T2I2 and we should see that we go back to using |
| // T2I1 rather than T2I2 |
| s.execute("CALL SYSCS_UTIL.SYSCS_DROP_STATISTICS('APP','T2','T2I2')"); |
| stats.assertIndexStats("T2I2", 0); |
| |
| //Rerunning the query "SELECT * FROM t2 WHERE c21=? AND c22=?" and |
| // looking at it's plan will show that this time it picked up T2I1 |
| // rather than more efficient index T2I2 because no stats exists |
| // for T2I2 |
| JDBC.assertDrainResults(ps.executeQuery()); |
| rtsp = SQLUtilities.getRuntimeStatisticsParser(s); |
| assertTrue(rtsp.usedSpecificIndexForIndexScan("T2","T2I1")); |
| |
| //cleanup |
| s.executeUpdate("DROP TABLE t2"); |
| //End of test case for better index selection after statistics |
| //availability |
| stats.release(); |
| } |
| |
| /** |
| * Test that SYSCS_UPDATE_STATISTICS doesn't obtain exclusive locks on |
| * the table or rows in the table (DERBY-4274). |
| */ |
| public void testNoExclusiveLockOnTable() throws SQLException { |
| Statement s = createStatement(); |
| s.execute("create table t (x char(1))"); |
| s.execute("create index ti on t(x)"); |
| s.execute("insert into t values 'a','b','c','d'"); |
| |
| setAutoCommit(false); |
| s.execute("lock table t in share mode"); |
| |
| Connection c2 = openDefaultConnection(); |
| Statement s2 = c2.createStatement(); |
| // This call used to time out because SYSCS_UPDATE_STATISTICS tried |
| // to lock T exclusively. |
| s2.execute("call syscs_util.syscs_update_statistics('APP', 'T', null)"); |
| s2.close(); |
| c2.close(); |
| |
| s.execute("drop table t"); |
| commit(); |
| } |
| |
| /** |
| * Regression test case for DERBY-5153. Compilation in parallel with |
| * update of statistics sometimes failed on debug builds. |
| */ |
| public void testParallelCompilationAndUpdate() throws Exception { |
| setAutoCommit(false); |
| |
| // Create and populate a test table with a multi-column index. |
| Statement s = createStatement(); |
| s.execute("create table derby5153(a int, b int, c int, d int)"); |
| s.execute("create index idx on derby5153(a,b,c,d)"); |
| |
| PreparedStatement ins = |
| prepareStatement("insert into derby5153 values (1,2,3,4)"); |
| for (int i = 0; i < 100; i++) { |
| ins.execute(); |
| } |
| |
| commit(); |
| |
| // Start a thread that repeatedly updates the statistics for IDX. |
| Connection updateConn = openDefaultConnection(); |
| IndexUpdateThread t = |
| new IndexUpdateThread(updateConn, "APP", "DERBY5153", "IDX"); |
| t.start(); |
| |
| try { |
| |
| // Compile/execute the query a number of times while the index |
| // statistics are being updated. This often failed with an assert |
| // failure in debug builds before DERBY-5153. |
| for (int i = 0; i < 100; i++) { |
| ResultSet rs = s.executeQuery( |
| "select * from derby5153 t1, derby5153 t2 " + |
| "where t1.a = t2.a"); |
| rs.close(); |
| } |
| |
| } finally { |
| |
| // Let the update thread know we're done. |
| t.done = true; |
| |
| } |
| |
| t.join(); |
| |
| // Check if the update thread failed, and report if it did. |
| if (t.exception != null) { |
| throw t.exception; |
| } |
| |
| updateConn.close(); |
| |
| // Clean-up |
| dropTable("derby5153"); |
| commit(); |
| } |
| |
| /** |
| * Fixed DERBY-5681(When a foreign key constraint on a table is dropped, |
| * the associated statistics row for the conglomerate is not removed.) |
| * @throws Exception |
| */ |
| public void testDERBY5681() throws Exception { |
| // Helper object to obtain information about index statistics. |
| IndexStatsUtil stats = new IndexStatsUtil(openDefaultConnection()); |
| Statement s = createStatement(); |
| |
| //Test - primary key constraint |
| s.executeUpdate("CREATE TABLE TEST_TAB_1 (c11 int not null,"+ |
| "c12 int not null, c13 int)"); |
| stats.assertNoStatsTable("TEST_TAB_1"); |
| //Insert data into table with no constraint and there will be no stat |
| // for that table at this point |
| s.executeUpdate("INSERT INTO TEST_TAB_1 VALUES(1,1,1),(2,2,2)"); |
| s.execute("CALL SYSCS_UTIL.SYSCS_UPDATE_STATISTICS('APP','TEST_TAB_1', null)"); |
| stats.assertNoStatsTable("TEST_TAB_1"); |
| // Add primary key constraint to the table. With DERBY-3790 this won't |
| // create a statistics entry, since the key consist of single column. |
| s.executeUpdate("ALTER TABLE TEST_TAB_1 "+ |
| "ADD CONSTRAINT TEST_TAB_1_PK_1 "+ |
| "PRIMARY KEY (c11)"); |
| stats.assertNoStatsTable("TEST_TAB_1"); |
| s.executeUpdate("ALTER TABLE TEST_TAB_1 "+ |
| "DROP CONSTRAINT TEST_TAB_1_PK_1"); |
| stats.assertNoStatsTable("TEST_TAB_1"); |
| s.execute("CALL SYSCS_UTIL.SYSCS_UPDATE_STATISTICS('APP','TEST_TAB_1', null)"); |
| stats.assertNoStatsTable("TEST_TAB_1"); |
| //Add the primary key constraint back since it will be used by the next |
| // test to create foreign key constraint |
| s.executeUpdate("ALTER TABLE TEST_TAB_1 "+ |
| "ADD CONSTRAINT TEST_TAB_1_PK_1 "+ |
| "PRIMARY KEY (c11)"); |
| stats.assertNoStatsTable("TEST_TAB_1"); |
| |
| //Test - unique key constraint |
| s.executeUpdate("ALTER TABLE TEST_TAB_1 "+ |
| "ADD CONSTRAINT TEST_TAB_1_UNQ_1 "+ |
| "UNIQUE (c12)"); |
| stats.assertNoStatsTable("TEST_TAB_1"); |
| s.executeUpdate("ALTER TABLE TEST_TAB_1 "+ |
| "DROP CONSTRAINT TEST_TAB_1_UNQ_1"); |
| stats.assertNoStatsTable("TEST_TAB_1"); |
| s.executeUpdate("ALTER TABLE TEST_TAB_1 "+ |
| "DROP CONSTRAINT TEST_TAB_1_PK_1"); |
| stats.assertNoStatsTable("TEST_TAB_1"); |
| s.executeUpdate("ALTER TABLE TEST_TAB_1 "+ |
| "ADD CONSTRAINT TEST_TAB_1_PK_1 "+ |
| "PRIMARY KEY (c11)"); |
| stats.assertNoStatsTable("TEST_TAB_1"); |
| |
| //Test - non-unique index |
| s.executeUpdate("CREATE INDEX TEST_TAB_1_NUNQ_1 ON TEST_TAB_1(c12)"); |
| stats.assertTableStats("TEST_TAB_1",1); |
| s.executeUpdate("DROP INDEX TEST_TAB_1_NUNQ_1"); |
| stats.assertNoStatsTable("TEST_TAB_1"); |
| s.executeUpdate("ALTER TABLE TEST_TAB_1 "+ |
| "DROP CONSTRAINT TEST_TAB_1_PK_1"); |
| stats.assertNoStatsTable("TEST_TAB_1"); |
| s.executeUpdate("ALTER TABLE TEST_TAB_1 "+ |
| "ADD CONSTRAINT TEST_TAB_1_PK_1 "+ |
| "PRIMARY KEY (c11)"); |
| stats.assertNoStatsTable("TEST_TAB_1"); |
| |
| //Test - unique key constraint on nullable column & non-nullable column |
| s.executeUpdate("ALTER TABLE TEST_TAB_1 "+ |
| "ADD CONSTRAINT TEST_TAB_1_UNQ_2 "+ |
| "UNIQUE (c12, c13)"); |
| stats.assertTableStats("TEST_TAB_1",2); |
| s.executeUpdate("ALTER TABLE TEST_TAB_1 "+ |
| "DROP CONSTRAINT TEST_TAB_1_UNQ_2"); |
| stats.assertNoStatsTable("TEST_TAB_1"); |
| s.executeUpdate("ALTER TABLE TEST_TAB_1 "+ |
| "DROP CONSTRAINT TEST_TAB_1_PK_1"); |
| stats.assertNoStatsTable("TEST_TAB_1"); |
| s.executeUpdate("ALTER TABLE TEST_TAB_1 "+ |
| "ADD CONSTRAINT TEST_TAB_1_PK_1 "+ |
| "PRIMARY KEY (c11)"); |
| stats.assertNoStatsTable("TEST_TAB_1"); |
| |
| //Test - foreign key but no primary key constraint |
| s.executeUpdate("CREATE TABLE TEST_TAB_3 (c31 int not null)"); |
| s.executeUpdate("INSERT INTO TEST_TAB_3 VALUES(1),(2)"); |
| s.executeUpdate("ALTER TABLE TEST_TAB_3 "+ |
| "ADD CONSTRAINT TEST_TAB_3_FK_1 "+ |
| "FOREIGN KEY(c31) REFERENCES TEST_TAB_1(c11)"); |
| stats.assertTableStats("TEST_TAB_3",1); |
| s.executeUpdate("ALTER TABLE TEST_TAB_3 "+ |
| "DROP CONSTRAINT TEST_TAB_3_FK_1"); |
| stats.assertNoStatsTable("TEST_TAB_3"); |
| |
| //Test - foreign key and primary key constraint |
| 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.assertNoStatsTable("TEST_TAB_2"); |
| // DERBY-5702 Add a foreign key constraint and now we should find one |
| // row of statistics for TEST_TAB_2 (for the 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.assertNoStatsTable("TEST_TAB_2"); |
| s.execute("CALL SYSCS_UTIL.SYSCS_UPDATE_STATISTICS('APP','TEST_TAB_2', null)"); |
| stats.assertTableStats("TEST_TAB_2",1); |
| //Number of statistics row for TEST_TAB_1 will remain unchanged since |
| // it has only primary key defined on it |
| stats.assertNoStatsTable("TEST_TAB_1"); |
| s.executeUpdate("ALTER TABLE TEST_TAB_2 "+ |
| "DROP CONSTRAINT TEST_TAB_2_FK_1"); |
| //Dropping the foreign key constraint should cause the statistics row |
| // for TEST_TAB_2 to be dropped as well. |
| stats.assertNoStatsTable("TEST_TAB_2"); |
| s.execute("CALL SYSCS_UTIL.SYSCS_UPDATE_STATISTICS('APP','TEST_TAB_2', null)"); |
| stats.assertNoStatsTable("TEST_TAB_2"); |
| s.execute("CALL SYSCS_UTIL.SYSCS_DROP_STATISTICS('APP','TEST_TAB_2', null)"); |
| //After DERBY-4115 is implemented, we will see no statistics |
| // for TEST_TAB_2 after calling SYSCS_DROP_STATISTICS on it. |
| stats.assertNoStatsTable("TEST_TAB_2"); |
| s.execute("drop table TEST_TAB_2"); |
| s.execute("drop table TEST_TAB_1"); |
| stats.release(); |
| } |
| |
| /** |
| * Tests that the functionality that drops disposable statistics leaves |
| * useful statistics intact. |
| */ |
| public void testDisposableStatsEagerness() |
| throws SQLException { |
| setAutoCommit(false); |
| String tbl = "DISPOSABLE_STATS_EAGERNESS"; |
| String tbl_fk = tbl + "_FK"; |
| String nuIdx = "NU_" + tbl; |
| Statement stmt = createStatement(); |
| |
| // Create and populate the foreign key table. |
| stmt.executeUpdate("create table " + tbl_fk + "(" + |
| "pk1 int generated always as identity)"); |
| PreparedStatement ps = prepareStatement( |
| "insert into " + tbl_fk + " values (DEFAULT)"); |
| for (int i=1; i <= 1000; i++) { |
| ps.executeUpdate(); |
| } |
| |
| // Create and populate the main table. |
| stmt.executeUpdate("create table " + tbl + "(" + |
| "pk1 int generated always as identity," + |
| "pk2 int not null," + |
| "mynonunique int, " + |
| "fk int not null)"); |
| ps = prepareStatement("insert into " + tbl + |
| " values (DEFAULT, ?, ?, ?)"); |
| for (int i=1; i <= 1000; i++) { |
| ps.setInt(1, i); |
| ps.setInt(2, i % 35); |
| ps.setInt(3, i); |
| ps.executeUpdate(); |
| } |
| |
| // Create the various indexes. |
| stmt.executeUpdate("alter table " + tbl_fk + " add constraint PK_" + |
| tbl_fk + " primary key (pk1)"); |
| |
| stmt.executeUpdate("alter table " + tbl + " add constraint PK_" + tbl + |
| " primary key (pk1, pk2)"); |
| stmt.executeUpdate("alter table " + tbl + " add constraint FK_" + tbl + |
| " foreign key (fk) references " + tbl_fk + "(pk1)"); |
| stmt.executeUpdate("create index " + nuIdx + " on " + tbl + |
| "(mynonunique)"); |
| commit(); |
| setAutoCommit(true); |
| IndexStatsUtil stats = new IndexStatsUtil(getConnection()); |
| // Expected FK table: 0 |
| // Expected main table: 2xPK, 1 non-unique, 1 FK = 4 |
| stats.assertNoStatsTable(tbl_fk); |
| stats.assertTableStats(tbl, 4); |
| IndexStatsUtil.IdxStats[] tbl_stats_0 = stats.getStatsTable(tbl); |
| |
| // Run the update statistics procedure. |
| // Sleep at least one tick to ensure the timestamps differ. |
| sleepAtLeastOneTick(); |
| ps = prepareStatement( |
| "call syscs_util.syscs_update_statistics('APP', ?, ?)"); |
| ps.setNull(2, Types.VARCHAR); |
| ps.setString(1, tbl); |
| ps.execute(); |
| ps.setString(1, tbl_fk); |
| ps.execute(); |
| |
| // Check the counts. |
| stats.assertNoStatsTable(tbl_fk); |
| stats.assertTableStats(tbl, 4); |
| // Check the timestamps (i.e. were they actually updated?). |
| IndexStatsUtil.IdxStats[] tbl_stats_1 = stats.getStatsTable(tbl); |
| assertEquals(tbl_stats_0.length, tbl_stats_1.length); |
| for (int i=0; i < tbl_stats_1.length; i++) { |
| assertTrue(tbl_stats_1[i].after(tbl_stats_0[i])); |
| } |
| |
| // Now make sure updating one index doesn't modify the others' stats. |
| sleepAtLeastOneTick(); |
| ps.setString(1, tbl); |
| ps.setString(2, nuIdx); |
| ps.execute(); |
| // Just use any of the previous stats as a reference point. |
| IndexStatsUtil.IdxStats nonUniqueIdx = stats.getStatsIndex(nuIdx)[0]; |
| assertTrue(nonUniqueIdx.after(tbl_stats_1[0])); |
| // Check the counts again. |
| stats.assertNoStatsTable(tbl_fk); |
| stats.assertTableStats(tbl, 4); |
| |
| // Cleanup |
| dropTable(tbl); |
| dropTable(tbl_fk); |
| } |
| |
| /** |
| * A thread class that repeatedly calls SYSCS_UTIL.SYSCS_UPDATE_STATISTICS |
| * until the flag {@code done} is set to true. Any exception thrown during |
| * the lifetime of the thread can be found in the field {@code exception}. |
| */ |
| private static class IndexUpdateThread extends Thread { |
| private final CallableStatement updateStats; |
| private volatile boolean done; |
| private Exception exception; |
| |
| private IndexUpdateThread( |
| Connection c, String schema, String table, String index) |
| throws SQLException { |
| updateStats = c.prepareCall( |
| "call syscs_util.syscs_update_statistics(?,?,?)"); |
| updateStats.setString(1, schema); |
| updateStats.setString(2, table); |
| updateStats.setString(3, index); |
| } |
| |
| public void run() { |
| try { |
| while (!done) { |
| updateStats.execute(); |
| } |
| updateStats.close(); |
| } catch (Exception e) { |
| this.exception = e; |
| } |
| } |
| } |
| } |