You are viewing a plain text version of this content. The canonical link for it is here.
Posted to derby-commits@db.apache.org by ma...@apache.org on 2013/06/28 20:23:25 UTC

svn commit: r1497868 - in /db/derby/code/branches/10.8: ./ java/engine/org/apache/derby/iapi/reference/ java/engine/org/apache/derby/iapi/sql/dictionary/ java/engine/org/apache/derby/impl/services/daemon/ java/engine/org/apache/derby/impl/sql/execute/ ...

Author: mamta
Date: Fri Jun 28 18:23:24 2013
New Revision: 1497868

URL: http://svn.apache.org/r1497868
Log:
DERBY-5680( indexStat daemon processing tables over and over even when there are no changes in the tables )

Backporting the 3 commits that went in for DERBY-5680 to 10.8. The 3 commits were 1340549, 1341622, 1341629. The first two commits were easy to backport using svn merge command but the third commit 1341629 ran into conflicts. For that backport, hand made the changes since there were not too many changes.

The changes for this jira has added a new property derby.storage.indexStats.debug.keepDisposableStats. The intention of the property is that if the property is set to true, we do not delete the orphaned/disposable stats. If the property is set to false, the orphaned/disposable stats will get dropped by the index stats daemon. Currently known reasons for orphaned/disposable stats are
1)DERBY-5681(When a foreign key constraint on a table is dropped, the associated statistics row for the conglomerate is not removed). Fix for this has been backported all the way to 10.3
2)DERBY-3790(Investigate if request for update statistics can be skipped for certain kind of indexes, one instance may be unique indexes based on one column.) Fix for this is in 10.9 and higher

A junit test was added for this new property but it went in as part of DERBY-3790. The name of the junit test is store.KeepDisposableStatsPropertyTest. Had to make changes to this test to backport it to 10.8 but without the fix for DEBRY-3790 and with the absence of drop statistics procedure, the test really does not make much sense for 10.8 codeline. The test uses drop statistics procedure and it is mainly testing DERBY-3790 to make sure that the orphaned stats are being deleted or left behind based on whether the property is set to true or false. But since we do not have drop statistics procedure and we do not have DERBY-3790 fixed in 10.8, we can't really meaningfully run the KeepDisposableStatsPropertyTest in 10.8. In any case, I have changed the test so that atleast it will not fail in 10.8 but it is not able to truly test the property. May be we can test this property through upgrade suite where we will create orphaned stats because of DERBY-5681 on older releases and w
 e will find that when the property is set to true, even after upgrade, we will have orphaned stats but when property is set to false, after upgrade, orphaned stats are deleted.
 

Added:
    db/derby/code/branches/10.8/java/testing/org/apache/derbyTesting/functionTests/tests/store/KeepDisposableStatsPropertyTest.java   (with props)
Modified:
    db/derby/code/branches/10.8/   (props changed)
    db/derby/code/branches/10.8/java/engine/org/apache/derby/iapi/reference/Property.java
    db/derby/code/branches/10.8/java/engine/org/apache/derby/iapi/sql/dictionary/TableDescriptor.java
    db/derby/code/branches/10.8/java/engine/org/apache/derby/impl/services/daemon/IndexStatisticsDaemonImpl.java
    db/derby/code/branches/10.8/java/engine/org/apache/derby/impl/sql/execute/AlterTableConstantAction.java
    db/derby/code/branches/10.8/java/testing/org/apache/derbyTesting/functionTests/tests/lang/UpdateStatisticsTest.java
    db/derby/code/branches/10.8/java/testing/org/apache/derbyTesting/functionTests/tests/store/_Suite.java

Propchange: db/derby/code/branches/10.8/
------------------------------------------------------------------------------
  Merged /db/derby/code/trunk:r1340549,1341622

Modified: db/derby/code/branches/10.8/java/engine/org/apache/derby/iapi/reference/Property.java
URL: http://svn.apache.org/viewvc/db/derby/code/branches/10.8/java/engine/org/apache/derby/iapi/reference/Property.java?rev=1497868&r1=1497867&r2=1497868&view=diff
==============================================================================
--- db/derby/code/branches/10.8/java/engine/org/apache/derby/iapi/reference/Property.java (original)
+++ db/derby/code/branches/10.8/java/engine/org/apache/derby/iapi/reference/Property.java Fri Jun 28 18:23:24 2013
@@ -711,6 +711,12 @@ public interface Property { 
             "derby.storage.indexStats.debug.queueSize";
     int STORAGE_AUTO_INDEX_STATS_DEBUG_QUEUE_SIZE_DEFAULT = 20;
 
+    /**
+     * Specifies whether to revert to 10.8 behavior and keep disposable stats.
+     */
+    String STORAGE_AUTO_INDEX_STATS_DEBUG_KEEP_DISPOSABLE_STATS =
+            "derby.storage.indexStats.debug.keepDisposableStats";
+
 	/*
 	** Transactions
 	*/

Modified: db/derby/code/branches/10.8/java/engine/org/apache/derby/iapi/sql/dictionary/TableDescriptor.java
URL: http://svn.apache.org/viewvc/db/derby/code/branches/10.8/java/engine/org/apache/derby/iapi/sql/dictionary/TableDescriptor.java?rev=1497868&r1=1497867&r2=1497868&view=diff
==============================================================================
--- db/derby/code/branches/10.8/java/engine/org/apache/derby/iapi/sql/dictionary/TableDescriptor.java (original)
+++ db/derby/code/branches/10.8/java/engine/org/apache/derby/iapi/sql/dictionary/TableDescriptor.java Fri Jun 28 18:23:24 2013
@@ -1301,7 +1301,7 @@ public class TableDescriptor extends Tup
 	
 	/** Returns a list of statistics for this table.
 	 */
-	private synchronized List getStatistics() throws StandardException
+	public synchronized List getStatistics() throws StandardException
 	{
 		// if table already has the statistics descriptors initialized
 		// no need to do anything

Modified: db/derby/code/branches/10.8/java/engine/org/apache/derby/impl/services/daemon/IndexStatisticsDaemonImpl.java
URL: http://svn.apache.org/viewvc/db/derby/code/branches/10.8/java/engine/org/apache/derby/impl/services/daemon/IndexStatisticsDaemonImpl.java?rev=1497868&r1=1497867&r2=1497868&view=diff
==============================================================================
--- db/derby/code/branches/10.8/java/engine/org/apache/derby/impl/services/daemon/IndexStatisticsDaemonImpl.java (original)
+++ db/derby/code/branches/10.8/java/engine/org/apache/derby/impl/services/daemon/IndexStatisticsDaemonImpl.java Fri Jun 28 18:23:24 2013
@@ -23,6 +23,7 @@ package org.apache.derby.impl.services.d
 import java.io.PrintWriter;
 import java.sql.Connection;
 import java.util.ArrayList;
+import java.util.List;
 
 import org.apache.derby.catalog.UUID;
 import org.apache.derby.catalog.types.StatisticsImpl;
@@ -118,6 +119,16 @@ public class IndexStatisticsDaemonImpl
                 Property.STORAGE_AUTO_INDEX_STATS_DEBUG_QUEUE_SIZE_DEFAULT);
     }
 
+    /**
+     * Tells if the user want us to fall back to pre 10.8 behavior.
+     * <p>
+     * This means do not drop any disposable statistics, and do not skip
+     * statistics for single-column primary key indexes.
+     */
+    private static final boolean FORCE_OLD_BEHAVIOR =
+            PropertyUtil.getSystemBoolean(
+            	Property.STORAGE_AUTO_INDEX_STATS_DEBUG_KEEP_DISPOSABLE_STATS);
+
     private final HeaderPrintWriter logStream;
     /** Tells if logging is enabled. */
     private final boolean doLog;
@@ -134,6 +145,8 @@ public class IndexStatisticsDaemonImpl
     private boolean daemonDisabled;
     /** The context manager for the worker thread. */
     private final ContextManager ctxMgr;
+    /** Tells if the database is older than 10.8 (for soft upgrade). */
+    private final boolean dbIsPre10_8;
     /** The language connection context for the worker thread. */
     private LanguageConnectionContext daemonLCC;
     /**
@@ -207,6 +220,7 @@ public class IndexStatisticsDaemonImpl
         this.traceToStdOut = (traceLevel.equalsIgnoreCase("both") ||
                 traceLevel.equalsIgnoreCase("stdout"));
         this.doTrace = traceToDerbyLog || traceToStdOut;
+        this.dbIsPre10_8 = checkIfDbIsPre10_8(db);
 
         this.db = db;
         this.dbOwner = userName;
@@ -225,6 +239,20 @@ public class IndexStatisticsDaemonImpl
                 "}) -> " + databaseName);
     }
 
+    /** Tells if the database is older than 10.8. */
+    private boolean checkIfDbIsPre10_8(Database db) {
+        try {
+            // Note the negation.
+            return !db.getDataDictionary().checkVersion(
+                DataDictionary.DD_VERSION_DERBY_10_8, null);
+        } catch (StandardException se) {
+            if (SanityManager.DEBUG) {
+                SanityManager.THROWASSERT("dd version check failed", se);
+            }
+            return true;
+        }
+    }
+
     /**
      * Schedules an update of the index statistics for the specified table.
      * <p>
@@ -319,8 +347,7 @@ public class IndexStatisticsDaemonImpl
         boolean lockConflictSeen = false;
         while (true) {
             try {
-                ConglomerateDescriptor[] cds = td.getConglomerateDescriptors();
-                updateIndexStatsMinion(lcc, td, cds, AS_BACKGROUND_TASK);
+                updateIndexStatsMinion(lcc, td, null, AS_BACKGROUND_TASK);
                 break;
             } catch (StandardException se) {
 
@@ -365,11 +392,17 @@ public class IndexStatisticsDaemonImpl
     /**
      * Updates the index statistics for the given table and the specified
      * indexes.
+     * <p>
+     * <strong>API note</strong>: Using {@code null} to update the statistics
+     * for all conglomerates is preferred over explicitly passing an array with
+     * all the conglomerates for the table. Doing so allows for some
+     * optimizations, and will cause a disposable statistics check to be
+     * performed.
      *
      * @param lcc language connection context used to perform the work
      * @param td the table to update index stats for
      * @param cds the conglomerates to update statistics for (non-index
-     *      conglomerates will be ignored)
+     *      conglomerates will be ignored), {@code null} means all indexes
      * @param asBackgroundTask whether the updates are done automatically as
      *      part of a background task or if explicitly invoked by the user
      * @throws StandardException if something goes wrong
@@ -379,6 +412,12 @@ public class IndexStatisticsDaemonImpl
                                         ConglomerateDescriptor[] cds,
                                         boolean asBackgroundTask)
             throws StandardException {
+        final boolean identifyDisposableStats =
+                (cds == null && !FORCE_OLD_BEHAVIOR && !dbIsPre10_8);
+        // Fetch descriptors if we're updating statistics for all indexes.
+        if (cds == null) {
+            cds = td.getConglomerateDescriptors();
+        }
         // Extract/derive information from the table descriptor
         long[] conglomerateNumber = new long[cds.length];
         ExecIndexRow[] indexRow = new ExecIndexRow[cds.length];
@@ -418,6 +457,53 @@ public class IndexStatisticsDaemonImpl
             heapCC.close();
         }
 
+        // Check for disposable statistics if we have the required information.
+        // Note that the algorithm would drop valid statistics entries if
+        // working on a subset of the table conglomerates/indexes.
+        if (identifyDisposableStats) {
+            List existingStats = td.getStatistics();
+            StatisticsDescriptor[] stats = (StatisticsDescriptor[])
+                    existingStats.toArray(
+                        new StatisticsDescriptor[existingStats.size()]);
+            // For now we know that disposable stats only exist in two cases,
+            // and that we'll only get one match for both of them per table:
+            //  a) orphaned statistics entries (i.e. DERBY-5681)
+            //  b) single-column primary keys (TODO: after DERBY-3790 is done)
+            for (int si=0; si < stats.length; si++) {
+                UUID referencedIndex = stats[si].getReferenceID();
+                boolean isValid = false;
+                for (int ci=0; ci < conglomerateNumber.length; ci++) {
+                    if (referencedIndex.equals(objectUUID[ci])) {
+                        isValid = true;
+                        break;
+                    }
+                }
+                // If the statistics entry is orphaned or not required, drop
+                // the statistics entries for this index. Those we really need
+                // will be rebuilt below. We expect this scenario to be rare,
+                // typically you would only see it on upgrades. On the other
+                // hand, this check is cheap enough such that it is feasible to
+                // do it as part of the stats update to get a "self healing"
+                // mechanism in case of another bug like DERBY-5681 in Derby.
+                if (!isValid) {
+                    String msg = "dropping disposable statistics entry " +
+                            stats[si].getUUID() + " for table " +
+                            stats[si].getTableUUID();
+                    logAlways(td, null, msg);
+                    trace(1, msg);
+                    DataDictionary dd = lcc.getDataDictionary();
+                    if (!lcc.dataDictionaryInWriteMode()) {
+                        dd.startWriting(lcc);
+                    }
+                    dd.dropStatisticsDescriptors(
+                            td.getUUID(), stats[si].getReferenceID(), tc); 
+                    if (asBackgroundTask) {
+                        lcc.internalCommit(true);
+                    }
+                }
+            }
+        }
+
         // [x][0] = conglomerate number, [x][1] = start time, [x][2] = stop time
         long[][] scanTimes = new long[conglomerateNumber.length][3];
         int sci = 0;
@@ -1086,18 +1172,29 @@ public class IndexStatisticsDaemonImpl
     private void log(boolean asBackgroundTask, TableDescriptor td, Throwable t,
             String msg) {
         if (asBackgroundTask && (doLog || t != null)) {
-            PrintWriter pw = null;
-            String hdrMsg = "{istat} " +
-                    (td == null ? "" : td.getQualifiedName() + ": ") + msg;
-            if (t != null) {
-                pw = new PrintWriter(logStream.getPrintWriter(), false);
-                pw.print(logStream.getHeader().getHeader());
-                pw.println(hdrMsg);
-                t.printStackTrace(pw);
-                pw.flush();
-            } else {
-                logStream.printlnWithHeader(hdrMsg);
-            }
+            logAlways(td, t, msg);
+        }
+    }
+
+    /**
+     * Logs the information given.
+     *
+     * @param td current table descriptor being worked on, may be {@code null}
+     * @param t raised error, may be {@code null}
+     * @param msg the message to log
+     */
+    private void logAlways(TableDescriptor td, Throwable t, String msg) {
+        PrintWriter pw;
+        String hdrMsg = "{istat} " +
+                (td == null ? "" : td.getQualifiedName() + ": ") + msg;
+        if (t != null) {
+            pw = new PrintWriter(logStream.getPrintWriter(), false);
+            pw.print(logStream.getHeader().getHeader());
+            pw.println(hdrMsg);
+            t.printStackTrace(pw);
+            pw.flush();
+        } else {
+            logStream.printlnWithHeader(hdrMsg);
         }
     }
 

Modified: db/derby/code/branches/10.8/java/engine/org/apache/derby/impl/sql/execute/AlterTableConstantAction.java
URL: http://svn.apache.org/viewvc/db/derby/code/branches/10.8/java/engine/org/apache/derby/impl/sql/execute/AlterTableConstantAction.java?rev=1497868&r1=1497867&r2=1497868&view=diff
==============================================================================
--- db/derby/code/branches/10.8/java/engine/org/apache/derby/impl/sql/execute/AlterTableConstantAction.java (original)
+++ db/derby/code/branches/10.8/java/engine/org/apache/derby/impl/sql/execute/AlterTableConstantAction.java Fri Jun 28 18:23:24 2013
@@ -662,7 +662,7 @@ class AlterTableConstantAction extends D
         td = dd.getTableDescriptor(tableId);
 
         if (updateStatisticsAll) {
-            cds = td.getConglomerateDescriptors();
+            cds = null;
         } else {
             cds = new ConglomerateDescriptor[1];
             cds[0] = dd.getConglomerateDescriptor(

Modified: db/derby/code/branches/10.8/java/testing/org/apache/derbyTesting/functionTests/tests/lang/UpdateStatisticsTest.java
URL: http://svn.apache.org/viewvc/db/derby/code/branches/10.8/java/testing/org/apache/derbyTesting/functionTests/tests/lang/UpdateStatisticsTest.java?rev=1497868&r1=1497867&r2=1497868&view=diff
==============================================================================
--- db/derby/code/branches/10.8/java/testing/org/apache/derbyTesting/functionTests/tests/lang/UpdateStatisticsTest.java (original)
+++ db/derby/code/branches/10.8/java/testing/org/apache/derbyTesting/functionTests/tests/lang/UpdateStatisticsTest.java Fri Jun 28 18:23:24 2013
@@ -27,6 +27,7 @@ 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;
@@ -56,7 +57,6 @@ public class UpdateStatisticsTest extend
         Test test = TestConfiguration.defaultSuite(UpdateStatisticsTest.class);
         Test statsDisabled = DatabasePropertyTestSetup.singleProperty
             ( test, "derby.storage.indexStats.auto", "false", true );
-
         return statsDisabled;
     }
 
@@ -388,6 +388,101 @@ public class UpdateStatisticsTest extend
     }
 
     /**
+     * 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: 1 (PK only)
+        // Expected main table: 2xPK, 1 non-unique, 1 FK = 4
+        stats.assertTableStats(tbl_fk, 1);
+        IndexStatsUtil.IdxStats tbl_fk_pk_0 = stats.getStatsTable(tbl_fk)[0];
+        stats.assertTableStats(tbl, 4);
+        IndexStatsUtil.IdxStats[] tbl_stats_0 = stats.getStatsTable(tbl);
+        // Avoid timestamp comparison problems on super-fast machines...
+        try {
+            Thread.sleep(10);
+        } catch (InterruptedException ie) {
+            Thread.currentThread().interrupt();
+        }
+
+        // Run the update statistics procedure.
+        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.assertTableStats(tbl_fk, 1);
+        stats.assertTableStats(tbl, 4);
+        // Check the timestamps (i.e. were they actually updated?).
+        IndexStatsUtil.IdxStats tbl_fk_pk_1 = stats.getStatsTable(tbl_fk)[0];
+        assertTrue(tbl_fk_pk_1.after(tbl_fk_pk_0));
+        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.
+        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.assertTableStats(tbl_fk, 1);
+        stats.assertTableStats(tbl, 4);
+    }
+
+    /**
      * 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}.

Added: db/derby/code/branches/10.8/java/testing/org/apache/derbyTesting/functionTests/tests/store/KeepDisposableStatsPropertyTest.java
URL: http://svn.apache.org/viewvc/db/derby/code/branches/10.8/java/testing/org/apache/derbyTesting/functionTests/tests/store/KeepDisposableStatsPropertyTest.java?rev=1497868&view=auto
==============================================================================
--- db/derby/code/branches/10.8/java/testing/org/apache/derbyTesting/functionTests/tests/store/KeepDisposableStatsPropertyTest.java (added)
+++ db/derby/code/branches/10.8/java/testing/org/apache/derbyTesting/functionTests/tests/store/KeepDisposableStatsPropertyTest.java Fri Jun 28 18:23:24 2013
@@ -0,0 +1,166 @@
+/*
+
+   Derby - Class org.apache.derbyTesting.functionTests.tests.store.KeepDisposableStatsPropertyTest
+
+   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.store;
+
+import java.sql.PreparedStatement;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.sql.Types;
+import java.util.Properties;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+import org.apache.derbyTesting.junit.BaseJDBCTestCase;
+import org.apache.derbyTesting.junit.IndexStatsUtil;
+import org.apache.derbyTesting.junit.SystemPropertyTestSetup;
+import org.apache.derbyTesting.junit.Utilities;
+/**
+ * Tests that the debug property used to revert to the previous behavior for
+ * dealing with disposable index cardinality statistics works.
+ */
+public class KeepDisposableStatsPropertyTest
+    extends BaseJDBCTestCase {
+
+    public KeepDisposableStatsPropertyTest(String name) {
+        super(name);
+    }
+
+    /** Verifies the behavior when the property is set to {@code true}. */
+    public void testPropertyFalse()
+            throws SQLException {
+        assertOnSCUI(false);
+    }
+
+    /** Verifies the behavior when the property is set to {@code true}. */
+    public void testPropertyTrue()
+            throws SQLException {
+        assertOnSCUI(true);
+    }
+
+    /** Verifies that the default for the property is {@code false}. */
+    public void testPropertyDefault()
+            throws SQLException {
+        assertOnSCUI(false);
+    }
+
+    /** Runs the real test case. */
+    private void assertOnSCUI(boolean keepDisposable)
+            throws SQLException {
+        IndexStatsUtil stats = new IndexStatsUtil(
+                openDefaultConnection(), 20*1000); // 20 seconds timeout
+        // Create table.
+        String TAB = "STAT_SCUI";
+        dropTable(TAB);
+        Statement stmt = createStatement();
+        stmt.executeUpdate("create table " + TAB +
+                " (id int not null, val int)");
+        stats.assertNoStatsTable(TAB);
+        PreparedStatement psIns = prepareStatement(
+                "insert into " + TAB + " values (?,?)");
+        setAutoCommit(false);
+        for (int i=0; i < 20; i++) {
+            psIns.setInt(1, i);
+            psIns.setInt(2, i);
+            psIns.executeUpdate();
+        }
+        commit();
+        setAutoCommit(true);
+        //Since DERBY-3790 is not fixed in 10.8, we will see the stats row
+        // for single column unique indexes in 10.8 dbs. Thus, in 10.8, the 
+        // use of keep disposable stats property really doesn't impact the
+        // number of rows in the stats table for single column unique indexes
+        // as can be seen for the following 2 indexes
+        stmt.executeUpdate("alter table " + TAB + " add constraint PK_" + TAB +
+                " primary key(id)");
+        stats.assertTableStats(TAB, keepDisposable ? 1  : 1);
+        stmt.executeUpdate(
+                "create unique index UNIQ_IDX_" + TAB + " ON " + TAB + "(val)");
+        stats.assertTableStats(TAB, keepDisposable ? 2  : 2);
+        PreparedStatement ps = prepareStatement(
+                "call SYSCS_UTIL.SYSCS_UPDATE_STATISTICS('APP', ?, ?)");
+        // Update stats for all indexes.
+        ps.setString(1, TAB);
+        ps.setNull(2, Types.VARCHAR);
+        ps.execute();
+        stats.assertTableStats(TAB, keepDisposable ? 2  : 2);
+
+        // Update stats for one specific index.
+        ps.setString(2, "UNIQ_IDX_" + TAB);
+        ps.execute();
+        stats.assertTableStats(TAB, keepDisposable ? 2  : 2);
+        IndexStatsUtil.IdxStats[] oldStats = stats.getStatsTable(TAB);
+        
+        // Insert more data to trigger an automatic update
+        setAutoCommit(false);
+        for (int i=20; i < 2000; i++) {
+            psIns.setInt(1, i);
+            psIns.setInt(2, i);
+            psIns.executeUpdate();
+        }
+        commit();
+        setAutoCommit(true);
+        // Trigger the scheduling logic to get the istat daemon going
+        prepareStatement("select * from " + TAB + " where id = ?"); 
+        Utilities.sleep(200);
+        IndexStatsUtil.IdxStats[] newStats =
+                stats.getStatsTable(TAB);
+        assertEquals(oldStats.length, newStats.length);
+        for (int i=0; i < oldStats.length; i++) {
+            assertEquals(true, newStats[i].after(oldStats[i]));
+        }
+
+        // Cleanup
+        dropTable(TAB);
+        stats.release();
+    }
+
+    /**
+     * Returns a suite where the test is run without specifying the property
+     * (use the default value), explicitly setting it to {@code true}, and
+     * explicitly setting it to {@code false}.
+     */
+    public static Test suite() {
+        String property = "derby.storage.indexStats.debug.keepDisposableStats";
+        TestSuite suite = new TestSuite("KeepDisposableStatsPropertyTestSuite");
+        // Test the default (expected to be false).
+        suite.addTest(
+                new KeepDisposableStatsPropertyTest("testPropertyDefault"));
+
+        // Test setting the property explicitly to true.
+        Properties propsOn = new Properties();
+        propsOn.setProperty(property, "true");
+        TestSuite suiteOn = new TestSuite("Do KeepDisposableStats");
+        suiteOn.addTest(
+                new KeepDisposableStatsPropertyTest("testPropertyTrue"));
+        suite.addTest(new SystemPropertyTestSetup(suiteOn, propsOn, true));
+
+        // Test setting the property explicitly to false.
+        Properties propsOff = new Properties();
+        propsOff.setProperty(property, "false");
+        TestSuite suiteOff = new TestSuite("Don't KeepDisposableStats");
+        suiteOff.addTest(
+                new KeepDisposableStatsPropertyTest("testPropertyFalse"));
+        suite.addTest(new SystemPropertyTestSetup(suiteOff, propsOff, true));
+
+        return suite;
+    }
+}

Propchange: db/derby/code/branches/10.8/java/testing/org/apache/derbyTesting/functionTests/tests/store/KeepDisposableStatsPropertyTest.java
------------------------------------------------------------------------------
    svn:eol-style = native

Modified: db/derby/code/branches/10.8/java/testing/org/apache/derbyTesting/functionTests/tests/store/_Suite.java
URL: http://svn.apache.org/viewvc/db/derby/code/branches/10.8/java/testing/org/apache/derbyTesting/functionTests/tests/store/_Suite.java?rev=1497868&r1=1497867&r2=1497868&view=diff
==============================================================================
--- db/derby/code/branches/10.8/java/testing/org/apache/derbyTesting/functionTests/tests/store/_Suite.java (original)
+++ db/derby/code/branches/10.8/java/testing/org/apache/derbyTesting/functionTests/tests/store/_Suite.java Fri Jun 28 18:23:24 2013
@@ -73,6 +73,7 @@ public class _Suite extends BaseTestCase
         suite.addTest(AutomaticIndexStatisticsTest.suite());
         suite.addTest(Derby5582AutomaticIndexStatisticsTest.suite());
         suite.addTest(Derby5234Test.suite());
+        suite.addTest(KeepDisposableStatsPropertyTest.suite());
         suite.addTest(AutomaticIndexStatisticsMultiTest.suite());
         suite.addTest(BTreeMaxScanTest.suite());
         suite.addTest(MadhareTest.suite());