/*
 * Class org.apache.derbyTesting.functionTests.tests.lang.SelectivityTest
 *
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

package org.apache.derbyTesting.functionTests.tests.lang;

import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;
import junit.framework.Test;
import org.apache.derbyTesting.junit.BaseJDBCTestCase;
import org.apache.derbyTesting.junit.BaseTestSuite;
import org.apache.derbyTesting.junit.CleanDatabaseTestSetup;
import org.apache.derbyTesting.junit.DatabasePropertyTestSetup;
import org.apache.derbyTesting.junit.JDBC;
import org.apache.derbyTesting.junit.RuntimeStatisticsParser;
import org.apache.derbyTesting.junit.SQLUtilities;

public class SelectivityTest extends BaseJDBCTestCase {

    public SelectivityTest(String name) {
        super(name);
    }
    
    public static Test suite() {
        
        Properties props = new Properties();
        // first disable the automatic statistics gathering so we get
        // clean statistics
        // then switch the statement cache size to 0, so that doesn't
        // interfere and previous tests' left-overs are gone.
        props.setProperty("derby.storage.indexStats.auto", "false");
        props.setProperty("derby.language.statementCacheSize", "0");
        // set the props, and boot the db
        Test test = new DatabasePropertyTestSetup(
            new BaseTestSuite(SelectivityTest.class), props, true);
        
        return new CleanDatabaseTestSetup(test) {
            protected void decorateSQL(Statement s) throws SQLException
            {        
                s.executeUpdate("create table two (x int)");
                s.executeUpdate("insert into two values (1),(2)");
                s.executeUpdate("create table ten (x int)");
                s
                        .executeUpdate("insert into ten values (1),(2),(3),(4),(5),(6),(7),(8),(9),(10)");
                s.executeUpdate("create table twenty (x int)");
                s
                        .executeUpdate("insert into twenty values (1),(2),(3),(4),(5),(6),(7),(8),(9),(10),(11),(12),(13),(14),(15),(16),(17),(18),(19),(20)");
                s
                        .executeUpdate("create table hundred (x int generated always as identity, dc int)");
                s
                        .executeUpdate("insert into hundred (dc) select t1.x from ten t1, ten t2");
                s
                        .executeUpdate("create table template (id int not null generated always as identity, two int, twenty int, hundred int)");
                // 4000 rows
                s
                        .executeUpdate("insert into template (two, twenty, hundred) select two.x, twenty.x, hundred.x from two, twenty, hundred");
                s.executeUpdate("create index template_two on template(two)");
                s
                        .executeUpdate("create index template_twenty on template(twenty)");
                // 20 distinct values
                s
                        .executeUpdate("create index template_22 on template(twenty,two)");
                s
                        .executeUpdate("create unique index template_id on template(id)");
                s
                        .executeUpdate("create index template_102 on template(hundred,two)");
                s
                        .executeUpdate("create table test (id int, two int, twenty int, hundred int)");
                s.executeUpdate("create index test_id on test(id)");
                s.executeUpdate("insert into test select * from template");

                s.executeUpdate("create view showstats as "
                                + "select cast (conglomeratename as varchar(60)) indexname, "
                                + "cast (statistics as varchar(60)) stats, "
                                + "creationtimestamp createtime, "
                                + "colcount ncols "
                                + "from sys.sysstatistics, sys.sysconglomerates "
                                + "where conglomerateid = referenceid");
                ResultSet statsrs = s
                        .executeQuery("select indexname, stats, ncols from showstats order by indexname, stats, createtime, ncols");
                JDBC.assertFullResultSet(statsrs, new String[][] {
                        {"TEMPLATE_102","numunique= 100 numrows= 4000","1"},
                        {"TEMPLATE_102","numunique= 200 numrows= 4000","2"},
                        {"TEMPLATE_22","numunique= 20 numrows= 4000","1"},
                        {"TEMPLATE_22","numunique= 40 numrows= 4000","2"},
                        {"TEMPLATE_TWENTY","numunique= 20 numrows= 4000","1"},
                        {"TEMPLATE_TWO","numunique= 2 numrows= 4000","1"}});               
                s
                        .executeUpdate("CALL SYSCS_UTIL.SYSCS_UPDATE_STATISTICS('APP','TEMPLATE',null)");
                s
                        .executeUpdate("CALL SYSCS_UTIL.SYSCS_UPDATE_STATISTICS('APP','TEST',null)");
                statsrs = s
                        .executeQuery("select indexname, stats, ncols from showstats order by indexname, stats, createtime, ncols");
                JDBC.assertFullResultSet(statsrs, new String[][] {
                        {"TEMPLATE_102","numunique= 100 numrows= 4000","1"},
                        {"TEMPLATE_102","numunique= 200 numrows= 4000","2"},
                        {"TEMPLATE_22","numunique= 20 numrows= 4000","1"},
                        {"TEMPLATE_22","numunique= 40 numrows= 4000","2"},
                        {"TEMPLATE_TWENTY","numunique= 20 numrows= 4000","1"},
                        {"TEMPLATE_TWO","numunique= 2 numrows= 4000","1"},
                        {"TEST_ID","numunique= 4000 numrows= 4000","1"},
                        });
                
                s.executeUpdate("create table t1 " +
                		"(id int generated always as identity, " +
                		"two int, twenty int, hundred varchar(3))");
                s.executeUpdate("insert into t1 (hundred, twenty, two) " +
                		"select CAST(CHAR(hundred.x) AS VARCHAR(3)), " +
                		"twenty.x, two.x from hundred, twenty, two");
                s.executeUpdate("create table t2 " +
                		"(id int generated always as identity, " +
                		"two int, twenty int, hundred varchar(3))");
                s.executeUpdate("insert into t2 (hundred, twenty, two) " +
                		"select CAST(CHAR(hundred.x) AS VARCHAR(3)) , " +
                		"twenty.x, two.x from hundred, twenty, two");
                s.executeUpdate("create table t3 " +
                		"(id int generated always as identity, " +
                		"two int, twenty int, hundred varchar(3))");
                s.executeUpdate("insert into t3 (hundred, twenty, two) " +
                		"select CAST(CHAR(hundred.x) AS VARCHAR(3)), " +
                		"twenty.x, two.x from hundred, twenty, two");
                s.executeUpdate("create index t1_hundred on t1(hundred)");
                s.executeUpdate("create index t1_two_twenty on t1(two,twenty)");
                s.executeUpdate("create index " +
                		"t1_twenty_hundred on t1(twenty, hundred)");
                s.executeUpdate("create index t2_hundred on t2(hundred)");
                s.executeUpdate("create index t2_two_twenty on t2(two,twenty)");
                s.executeUpdate("create index t2_twenty_hundred on t2(twenty, hundred)");
                s.executeUpdate("create index t3_hundred on t3(hundred)");
                s.executeUpdate("create index t3_two_twenty on t3(two,twenty)");
                s.executeUpdate("create index t3_twenty_hundred on t3(twenty, hundred)");
                s.executeUpdate("CALL SYSCS_UTIL.SYSCS_UPDATE_STATISTICS" +
                        "('APP','T1',null)");
                s.executeUpdate("CALL SYSCS_UTIL.SYSCS_UPDATE_STATISTICS" +
                        "('APP','T2',null)");
                s.executeUpdate("CALL SYSCS_UTIL.SYSCS_UPDATE_STATISTICS" +
                        "('APP','T3',null)");

                statsrs = s.executeQuery(
                        "select indexname, stats, ncols from showstats " +
                        "where indexname like 'T1%' " +
                        "order by indexname, stats");
                JDBC.assertFullResultSet(statsrs, new String[][] {
                        {"T1_HUNDRED","numunique= 100 numrows= 4000","1"},
                        {"T1_TWENTY_HUNDRED","numunique= 20 numrows= 4000","1"},
                        {"T1_TWENTY_HUNDRED","numunique= 2000 numrows= 4000","2"},
                        {"T1_TWO_TWENTY","numunique= 2 numrows= 4000","1"},
                        {"T1_TWO_TWENTY","numunique= 40 numrows= 4000","2"}});
                statsrs = s.executeQuery(
                        "select indexname, stats, ncols from showstats " +
                        "where indexname like 'T2%' order by indexname, stats");
                JDBC.assertFullResultSet(statsrs, new String[][] {
                        {"T2_HUNDRED","numunique= 100 numrows= 4000","1"},
                        {"T2_TWENTY_HUNDRED","numunique= 20 numrows= 4000","1"},
                        {"T2_TWENTY_HUNDRED","numunique= 2000 numrows= 4000","2"},
                        {"T2_TWO_TWENTY","numunique= 2 numrows= 4000","1"},
                        {"T2_TWO_TWENTY","numunique= 40 numrows= 4000","2"}});
                statsrs = s.executeQuery(
                        "select indexname, stats, ncols from showstats " +
                        "where indexname like 'T3%' order by indexname, stats");
                JDBC.assertFullResultSet(statsrs, new String[][] {
                        {"T3_HUNDRED","numunique= 100 numrows= 4000","1"},
                        {"T3_TWENTY_HUNDRED","numunique= 20 numrows= 4000","1"},
                        {"T3_TWENTY_HUNDRED","numunique= 2000 numrows= 4000","2"},
                        {"T3_TWO_TWENTY","numunique= 2 numrows= 4000","1"},
                        {"T3_TWO_TWENTY","numunique= 40 numrows= 4000","2"}});
                
                s.executeUpdate("create table scratch_table" +
                        "(id int, two int, twenty int, hundred int)");
                s.executeUpdate("insert into scratch_table select " +
                        "id, two, twenty, CAST(CHAR(hundred) AS INTEGER) " +
                        "from t1");
                s.executeUpdate("create index st_all on scratch_table" +
                        "(two, twenty, hundred)");
                s.executeUpdate("CALL SYSCS_UTIL.SYSCS_UPDATE_STATISTICS" +
                        "('APP','SCRATCH_TABLE',null)");
                
                s.executeUpdate("create table complex" +
                        "(id int generated always as identity, " +
                        "two int, twenty int, hundred int, a int, b int)");
                s.executeUpdate(
                        "insert into complex (two, twenty, hundred, a, b) " +
                        "select two.x, twenty.x, hundred.x, two.x, twenty.x " +
                        "from two, twenty, hundred");
                s.executeUpdate("create index complexind on complex" +
                        "(two, twenty, hundred, a, b)");
                s.executeUpdate("CALL SYSCS_UTIL.SYSCS_UPDATE_STATISTICS" +
                        "('APP','COMPLEX',null)");

            }
        };
    }
    
    public void testSingleColumnSelectivity() throws SQLException {
        // choose whatever plan you want but the row estimate should be.
        //(n * n) * 0.5
        // join on two, template inner, all rows.
        Connection conn = getConnection();
        Statement s = createStatement();
        s.executeUpdate("CALL SYSCS_UTIL.SYSCS_UPDATE_STATISTICS" +
                "('APP','TEMPLATE',NULL)");
        s.executeUpdate("CALL SYSCS_UTIL.SYSCS_UPDATE_STATISTICS" +
                "('APP','TEST',NULL)");
        s.executeUpdate("call SYSCS_UTIL.SYSCS_SET_RUNTIMESTATISTICS(1)");
        s.executeQuery("select template.id from --DERBY-PROPERTIES joinOrder=fixed\n" 
                + "test, template where test.two = template.two").close();
        checkEstimatedRowCount(conn,8020012.5);
        
        // choose hash join. Selectivity should be the same
        // join on two. template inner, hash join
        s.executeQuery("select template.id from " +
                "--DERBY-PROPERTIES joinOrder=fixed \n" + 
                "test, template --DERBY-PROPERTIES joinStrategy=hash \n" +
                "where test.two = template.two").close();
        checkEstimatedRowCount(conn,8020012.5);
        RuntimeStatisticsParser rtsp = 
              SQLUtilities.getRuntimeStatisticsParser(s);
        assertTrue(rtsp.usedHashJoin());
        
        // choose NL join, no index. Selectivity should be the same
        // join on two. template inner, NL, no index, all rows.
        s.executeQuery("select template.id from " +
                "--DERBY-PROPERTIES joinOrder=fixed \n" + 
                "test, template --DERBY-PROPERTIES joinStrategy=nestedLoop, " +
                "index=null \n" +
                "where test.two = template.two").close();
        checkEstimatedRowCount(conn,8020012.5);
        rtsp = SQLUtilities.getRuntimeStatisticsParser(s);
        assertFalse(rtsp.usedHashJoin());
        
        // choose NL join, index template_two. Selectivity should be the same
        // join on two. template inner, NL, index=two, all rows.
        s.executeQuery("select template.id from " +
                "--DERBY-PROPERTIES joinOrder=fixed \n" + 
                "test, template --DERBY-PROPERTIES joinStrategy=nestedLoop, " +
                "index=template_two \n" +
                "where test.two = template.two").close();
        checkEstimatedRowCount(conn,8020012.5);
        rtsp = SQLUtilities.getRuntimeStatisticsParser(s);
        assertTrue(rtsp.usedSpecificIndexForIndexScan("TEMPLATE", "TEMPLATE_TWO"));
        
        // do joins on 20
        // first NL
        // join on twenty. template inner, NL, index=template_twenty, all rows.
        s.executeQuery("select template.id from " +
                "--DERBY-PROPERTIES joinOrder=fixed \n" + 
                "test, template --DERBY-PROPERTIES joinStrategy=nestedLoop, " +
                "index=template_twenty \n" +
                "where test.twenty = template.twenty").close();
        // Rowcount should be same as testSingleColumnSelectivityHash
        checkEstimatedRowCount(conn,802001.25);
        rtsp = SQLUtilities.getRuntimeStatisticsParser(s);
        assertTrue(rtsp.usedSpecificIndexForIndexScan("TEMPLATE", "TEMPLATE_TWENTY"));
        
        // join on 20 but use index 20_2
        // cost as well as selectivity should be divided using selectivity
        // cost should same as template_twenty, or just a shade more...
        // join on twenty. template inner, NL, index=template_22, all rows
        s.executeQuery("select template.id from " +
                "--DERBY-PROPERTIES joinOrder=fixed \n" + 
                "test, template --DERBY-PROPERTIES joinStrategy=nestedLoop, " +
                "index=template_22 \n" +
                "where test.twenty = template.twenty").close();
        checkEstimatedRowCount(conn,802001.25);
        rtsp = SQLUtilities.getRuntimeStatisticsParser(s);
        assertTrue(rtsp.usedSpecificIndexForIndexScan("TEMPLATE", "TEMPLATE_22"));
        
        // join on twenty but no index
        // note: the original test had this comment:
        // 'rc should be divided using selectivity. cost should be way different'
        // however, it seems the ec is identical.
        // join on twenty, template inner, NL, index=null, all rows
        s.executeQuery("select template.id from " +
                "--DERBY-PROPERTIES joinOrder=fixed \n" + 
                "test, template --DERBY-PROPERTIES joinStrategy=nestedLoop, " +
                "index=null \n" +
                "where test.twenty = template.twenty").close();
        checkEstimatedRowCount(conn,802001.25);
        
        // still single column, try stuff on 100 but with extra qualification
        // on outer table.
        // row count is 100 * 4000 * 0.01 = 4000
        // join on hundred. 
        // template inner, NL, index=template_102, 100 rows from outer
        s.executeQuery("select template.id from " +
                "--DERBY-PROPERTIES joinOrder=fixed \n" + 
                "test, template --DERBY-PROPERTIES joinStrategy=nestedLoop, " +
                "index=template_102 \n" +
                "where test.hundred = template.hundred and test.id <= 100").close();
        // note: original cloudscape result was expecting 3884.85 here.
        checkEstimatedRowCount(conn,3924.9);
        rtsp = SQLUtilities.getRuntimeStatisticsParser(s);
        assertTrue(rtsp.usedSpecificIndexForIndexScan("TEST", "TEST_ID"));
        assertTrue(rtsp.usedSpecificIndexForIndexScan("TEMPLATE", "TEMPLATE_102"));
        
        // join on hundred. 
        // template inner, NL, index=null, 100 rows from outer
        s.executeQuery("select template.id from " +
                "--DERBY-PROPERTIES joinOrder=fixed \n" + 
                "test, template --DERBY-PROPERTIES joinStrategy=nestedLoop, " +
                "index=null \n" +
                "where test.hundred = template.hundred and test.id <= 100").close();
        checkEstimatedRowCount(conn,3924.9);
        rtsp = SQLUtilities.getRuntimeStatisticsParser(s);
        assertTrue(rtsp.usedSpecificIndexForIndexScan("TEST", "TEST_ID"));
        
        // join on hundred. 
        // template inner, hash, index=null, 100 rows from outer.
        s.executeQuery("select template.id from " +
                "--DERBY-PROPERTIES joinOrder=fixed \n" + 
                "test, template --DERBY-PROPERTIES joinStrategy=hash, " +
                "index=null \n" +
                "where test.hundred = template.hundred and test.id <= 100").close();
        checkEstimatedRowCount(conn,3924.9);
        rtsp = SQLUtilities.getRuntimeStatisticsParser(s);
        assertTrue(rtsp.usedHashJoin());
        assertTrue(rtsp.usedSpecificIndexForIndexScan("TEST", "TEST_ID"));
    }
    
    public void testMultiPredicate() throws SQLException {
        // multi predicate tests.
        // first do a oin involving twenty and two
        // forde use of a simngle column index to do the join
        // the row count should involve statistics from both 10 and 2 though...
        
        // row count should 4K * 4K * 1/40 = 400,000
        // cost doesn't show up in output but should depend on the index
        // being used (verify by hand before checking in.)
        setAutoCommit(false);
        Connection conn = getConnection();
        Statement s = createStatement();
        
        // join on twenty/two. template inner, hash, index=null, all rows.
        s.executeUpdate("CALL SYSCS_UTIL.SYSCS_UPDATE_STATISTICS" +
                "('APP','TEST',NULL)");
        s.executeUpdate("CALL SYSCS_UTIL.SYSCS_UPDATE_STATISTICS" +
                "('APP','TEMPLATE',NULL)");
        s.executeUpdate("call SYSCS_UTIL.SYSCS_SET_RUNTIMESTATISTICS(1)");
        s.executeQuery("select template.id from " +
                "--DERBY-PROPERTIES joinOrder=fixed \n" + 
                "test, template --DERBY-PROPERTIES joinStrategy=hash, " +
                "index=null \n" +
                "where test.twenty = template.twenty " +
                "and test.two = template.two").close();
        checkEstimatedRowCount(conn,401000.625);
        RuntimeStatisticsParser rtsp = 
                SQLUtilities.getRuntimeStatisticsParser(s);
        assertTrue(rtsp.usedHashJoin());
        
        // join on twenty/two. template inner, NL, index=template_two, all rows
        s.executeQuery("select template.id from " +
                "--DERBY-PROPERTIES joinOrder=fixed \n" + 
                "test, template --DERBY-PROPERTIES joinStrategy=nestedLoop, " +
                "index=template_two \n" +
                "where test.twenty = template.twenty " +
                "and test.two = template.two").close();
        checkEstimatedRowCount(conn,401000.625);
        rtsp = SQLUtilities.getRuntimeStatisticsParser(s);
        assertTrue(rtsp.usedSpecificIndexForIndexScan("TEMPLATE", "TEMPLATE_TWO"));
        
        // join on twenty/two. 
        // template inner, NL, index=template_twenty, all rows.
        s.executeQuery("select template.id from " +
                "--DERBY-PROPERTIES joinOrder=fixed \n" + 
                "test, template --DERBY-PROPERTIES joinStrategy=nestedLoop, " +
                "index=template_twenty \n" +
                "where test.twenty = template.twenty " +
                "and test.two = template.two").close();
        checkEstimatedRowCount(conn,401000.625);
        rtsp = SQLUtilities.getRuntimeStatisticsParser(s);
        assertTrue(rtsp.usedSpecificIndexForIndexScan("TEMPLATE", "TEMPLATE_TWENTY"));
        
        // join on twenty/two. template inner, NL, index=template_22, all rows.
        s.executeQuery("select template.id from " +
                "--DERBY-PROPERTIES joinOrder=fixed \n" + 
                "test, template --DERBY-PROPERTIES joinStrategy=nestedLoop, " +
                "index=template_22 \n" +
                "where test.twenty = template.twenty " +
                "and test.two = template.two").close();
        checkEstimatedRowCount(conn,401000.625);
        rtsp = SQLUtilities.getRuntimeStatisticsParser(s);
        assertTrue(rtsp.usedSpecificIndexForIndexScan("TEMPLATE", "TEMPLATE_22"));
        
        // multi predicate tests continued
        // drop index twenty, two -- use above predicates
        // should be smart enough to figure out the selectivity by
        // combining twenty and two.
        s.executeUpdate("drop index template_22");
        
        // join on twenty/two. index twenty_two dropped. 
        // template inner, hash, index=null, all rows.
        s.executeQuery("select template.id from " +
                "--DERBY-PROPERTIES joinOrder=fixed \n" + 
                "test, template --DERBY-PROPERTIES joinStrategy=hash, " +
                "index=null \n" +
                "where test.twenty = template.twenty " +
                "and test.two = template.two").close();
        checkEstimatedRowCount(conn,401000.625);
        rtsp = SQLUtilities.getRuntimeStatisticsParser(s);
        assertTrue(rtsp.usedHashJoin());
        
        // join on twenty/two. index twenty_two dropped. 
        // template inner, NL, index=template_two, all rows.'
        s.executeQuery("select template.id from " +
                "--DERBY-PROPERTIES joinOrder=fixed \n" + 
                "test, template --DERBY-PROPERTIES joinStrategy=nestedLoop, " +
                "index=template_two \n" +
                "where test.twenty = template.twenty " +
                "and test.two = template.two").close();
        checkEstimatedRowCount(conn,401000.625);
        rtsp = SQLUtilities.getRuntimeStatisticsParser(s);
        assertTrue(rtsp.usedSpecificIndexForIndexScan("TEMPLATE", "TEMPLATE_TWO"));
        
        // join on twenty/two. index twenty_two dropped. 
        // template inner, NL, index=template_twenty, all rows.
        s.executeQuery("select template.id from " +
                "--DERBY-PROPERTIES joinOrder=fixed \n" + 
                "test, template --DERBY-PROPERTIES joinStrategy=nestedLoop, " +
                "index=template_twenty \n" +
                "where test.twenty = template.twenty " +
                "and test.two = template.two").close();
        checkEstimatedRowCount(conn,401000.625);
        rtsp = SQLUtilities.getRuntimeStatisticsParser(s);
        assertTrue(rtsp.usedSpecificIndexForIndexScan("TEMPLATE", "TEMPLATE_TWENTY"));
        
        s.executeUpdate("drop index template_two");
        
        // we only have index template_twenty
        // for the second predicate we should use 0.1 instead of 0.5
        // thus reducing earlier row count by a factor of 5
        // 80,000 instead of 400,000
        
        // join on twenty/two. index twenty_two and two dropped. 
        // template inner, NL, index=null, all rows.
        s.executeQuery("select template.id from " +
                "--DERBY-PROPERTIES joinOrder=fixed \n" + 
                "test, template --DERBY-PROPERTIES joinStrategy=nestedLoop, " +
                "index=null \n" +
                "where test.twenty = template.twenty " +
                "and test.two = template.two").close();
        checkEstimatedRowCount(conn,80200.12500000001);
        
        // join on twenty/two. index twenty_two and two dropped. 
        // template inner, NL, index=template_twenty, all rows.
        s.executeQuery("select template.id from " +
                "--DERBY-PROPERTIES joinOrder=fixed \n" + 
                "test, template --DERBY-PROPERTIES joinStrategy=nestedLoop, " +
                "index=template_twenty \n" +
                "where test.twenty = template.twenty " +
                "and test.two = template.two").close();
        checkEstimatedRowCount(conn,80200.12500000001);
        rtsp = SQLUtilities.getRuntimeStatisticsParser(s);
        assertTrue(rtsp.usedSpecificIndexForIndexScan("TEMPLATE", "TEMPLATE_TWENTY"));
        
        // now drop index template_twenty
        // selectivity should become 0.1 * 0.1 = 0.01
        // 16 * 10^6 * .01 = 160,000
        
        s.executeUpdate("drop index template_twenty");
        
        // join on twenty/two. all indexes dropped.
        // template inner, NL, index=null, all rows.
        s.executeQuery("select template.id from " +
                "--DERBY-PROPERTIES joinOrder=fixed \n" + 
                "test, template --DERBY-PROPERTIES joinStrategy=nestedLoop, " +
                "index=null \n" +
                "where test.twenty = template.twenty " +
                "and test.two = template.two").close();
        checkEstimatedRowCount(conn,160400.25000000003);
        rollback();
    } 
    
    public void testTwoWayJoins() throws SQLException {
        setAutoCommit(false);
        Connection conn = getConnection();
        Statement s = createStatement();
        
        // throw in aditional predicates
        // see that the optimizer does the right thing
        
        // index on template_102. join on hundred, constant predicate on two. 
        // should be able to use statistics for hundred_two to com up with
        // row estimate.
        
        // selectivity should be 0.01 * 0.5 = 0.005
        // row count is 16*10^6 * 0.005 = 8*10^4.
        
        // join on hundred. constant pred on two. NL, index=null, all rows.
        s.executeUpdate("CALL SYSCS_UTIL.SYSCS_UPDATE_STATISTICS" +
                "('APP','TEMPLATE',NULL)");
        s.executeUpdate("CALL SYSCS_UTIL.SYSCS_UPDATE_STATISTICS" +
                "('APP','TEST',NULL)");
        s.executeUpdate("call SYSCS_UTIL.SYSCS_SET_RUNTIMESTATISTICS(1)");
        s.executeQuery("select template.id from " +
                "--DERBY-PROPERTIES joinOrder=fixed \n" + 
                "test, template --DERBY-PROPERTIES joinStrategy=nestedLoop, " +
                "index=null \n" +
                "where test.hundred = template.hundred " +
                "and 1 = template.two").close();
        checkEstimatedRowCount(conn,80200.125);
        
        // just retry above query with different access paths
        // row count shouldn't change!
        // join on hundred. constant pred on two. 
        // NL, index=template_102, all rows.
        s.executeUpdate("call SYSCS_UTIL.SYSCS_SET_RUNTIMESTATISTICS(1)");
        s.executeQuery("select template.id from " +
                "--DERBY-PROPERTIES joinOrder=fixed \n" + 
                "test, template --DERBY-PROPERTIES joinStrategy=nestedLoop, " +
                "index=template_102 \n" +
                "where test.hundred = template.hundred " +
                "and 1 = template.two").close();
        checkEstimatedRowCount(conn,80200.125);
        RuntimeStatisticsParser rtsp = 
                SQLUtilities.getRuntimeStatisticsParser(s);
        assertTrue(rtsp.usedSpecificIndexForIndexScan("TEMPLATE", "TEMPLATE_102"));
        
        // hundred and twenty
        // we can use statistics for 100,2 to get selectivity for 100 and
        // twenty and twenty to get selectivity for 20
        // selectivity should 0.01 * 0.05 = 0.0005 -> 80,000
        // join on hundred. constant pred on twenty. 
        // NL, index=null, all rows.
        s.executeQuery("select template.id from " +
                "--DERBY-PROPERTIES joinOrder=fixed \n" + 
                "test, template --DERBY-PROPERTIES joinStrategy=nestedLoop, " +
                "index=null \n" +
                "where test.hundred = template.hundred " +
                "and 1 = template.twenty").close();
        checkEstimatedRowCount(conn,8020.0125);
        
        // 'join on hundred. constant pred on twenty. 
        // NL, index=template_102 all rows.
        s.executeQuery("select template.id from " +
                "--DERBY-PROPERTIES joinOrder=fixed \n" + 
                "test, template --DERBY-PROPERTIES joinStrategy=nestedLoop, " +
                "index=template_102 \n" +
                "where test.hundred = template.hundred " +
                "and 1 = template.twenty").close();
        checkEstimatedRowCount(conn,8020.0125);
        rtsp = SQLUtilities.getRuntimeStatisticsParser(s);
        assertTrue(rtsp.usedSpecificIndexForIndexScan("TEMPLATE", "TEMPLATE_102"));
    }
    
    public void testThreeWayJoins() throws SQLException {
        setAutoCommit(false);
        Connection conn = getConnection();
        Statement s = createStatement();
        
        // t1 x t2 yields 8000 rows.
        // x t3 yields 8*4 * 10^6 /2 = 16*10^6
        s.executeUpdate("CALL SYSCS_UTIL.SYSCS_UPDATE_STATISTICS" +
                "('APP','T1',NULL)");
        s.executeUpdate("CALL SYSCS_UTIL.SYSCS_UPDATE_STATISTICS" +
                "('APP','T2',NULL)");
        s.executeUpdate("CALL SYSCS_UTIL.SYSCS_UPDATE_STATISTICS" +
                "('APP','T3',NULL)");
        s.executeUpdate("call SYSCS_UTIL.SYSCS_SET_RUNTIMESTATISTICS(1)");
        s.executeQuery("select t1.id from " +
                "--DERBY-PROPERTIES joinOrder=fixed \n" + 
                "t1, t2, t3 " +
                "where t1.hundred = t2.hundred " +
                "and t1.twenty = t2.twenty and " +
                "t2.two = t3.two").close();
        checkEstimatedRowCount(conn,1.606007503125E7);
        RuntimeStatisticsParser rtsp = 
                SQLUtilities.getRuntimeStatisticsParser(s);
        assertTrue(rtsp.findString("Table Scan ResultSet for T1", 1));
        assertTrue(rtsp.findString("Bit set of columns fetched={0, 2, 3}", 1));
        assertTrue(rtsp.findString("Hash Scan ResultSet for T2", 1));
        assertTrue(rtsp.findString("Bit set of columns fetched={1, 2, 3}", 1));
        assertTrue(rtsp.findString(
                "Hash Scan ResultSet for T3 using index T3_TWO_TWENTY", 1));
        
        // t1 x t2 -> 16 * 10^4.
        // x t3    -> 32 * 10^7
        // additional pred -> 32 * 10^5
        s.executeQuery("select t1.id from " +
                "--DERBY-PROPERTIES joinOrder=fixed \n" + 
                "t1, t2, t3 " +
                "where t1.hundred = t2.hundred " +
                "and t2.two = t3.two and " +
                "t1.hundred = t3.hundred").close();
        checkEstimatedRowCount(conn,3212015.00625);
        rtsp = SQLUtilities.getRuntimeStatisticsParser(s);
        assertTrue(rtsp.findString("Table Scan ResultSet for T1", 1));
        assertTrue(rtsp.findString("Bit set of columns fetched={0, 3}", 1));
        assertTrue(rtsp.findString("Hash Scan ResultSet for T2", 1));
        assertTrue(rtsp.findString("Bit set of columns fetched={1, 3}", 1));
        assertTrue(rtsp.findString("Hash Scan ResultSet for T3", 1));
        assertTrue(rtsp.findString("Bit set of columns fetched={1, 3}", 1));
        
        // variations on above query; try different join strategies
        s.executeQuery("select t1.id from " +
                "--DERBY-PROPERTIES joinOrder=fixed \n" + 
                "t1, t2, t3 --DERBY-PROPERTIES joinStrategy=hash \n" +
                "where t1.hundred = t2.hundred " +
                "and t2.two = t3.two " +
                "and t1.hundred = t3.hundred").close();
        checkEstimatedRowCount(conn,3212015.00625);
        rtsp = SQLUtilities.getRuntimeStatisticsParser(s);
        assertTrue(rtsp.findString("Table Scan ResultSet for T1", 1));
        assertTrue(rtsp.findString("Bit set of columns fetched={0, 3}", 1));
        assertTrue(rtsp.findString("Hash Scan ResultSet for T2", 1));
        assertTrue(rtsp.findString("Bit set of columns fetched={1, 3}", 1));
        assertTrue(rtsp.findString("Hash Scan ResultSet for T3", 1));
        assertTrue(rtsp.findString("Bit set of columns fetched={1, 3}", 1));
        
        s.executeQuery("select t1.id from " +
                "--DERBY-PROPERTIES joinOrder=fixed \n" + 
                "t1, t2, t3 --DERBY-PROPERTIES joinStrategy=nestedLoop \n" +
                "where t1.hundred = t2.hundred " +
                "and t2.two = t3.two " +
                "and t1.hundred = t3.hundred").close();
        checkEstimatedRowCount(conn,3212015.00625);
        rtsp = SQLUtilities.getRuntimeStatisticsParser(s);
        assertTrue(rtsp.findString("Table Scan ResultSet for T1", 1));
        assertTrue(rtsp.findString("Bit set of columns fetched={0, 3}", 1));
        assertTrue(rtsp.findString("Hash Scan ResultSet for T2", 1));
        assertTrue(rtsp.findString("Bit set of columns fetched={1, 3}", 1));
        assertTrue(rtsp.usedSpecificIndexForIndexScan("T3", "T3_HUNDRED"));
        assertTrue(rtsp.findString("Bit set of columns fetched=All", 1));
        
        s.executeQuery("select t1.id from " +
                "--DERBY-PROPERTIES joinOrder=fixed \n" + 
                "t1, t2 --DERBY-PROPERTIES joinStrategy=hash \n, t3 " +
                "where t1.hundred = t2.hundred " +
                "and t2.two = t3.two " +
                "and t1.hundred = t3.hundred").close();
        checkEstimatedRowCount(conn,3212015.00625);
        rtsp = SQLUtilities.getRuntimeStatisticsParser(s);
        assertTrue(rtsp.findString("Table Scan ResultSet for T1", 1));
        assertTrue(rtsp.findString("Bit set of columns fetched={0, 3}", 1));
        assertTrue(rtsp.findString("Hash Scan ResultSet for T2", 1));
        assertTrue(rtsp.findString("Bit set of columns fetched={1, 3}", 1));
        assertTrue(rtsp.findString("Hash Scan ResultSet for T3", 1));
        assertTrue(rtsp.findString("Bit set of columns fetched={1, 3}", 1));
        
        s.executeQuery("select t1.id from " +
                "--DERBY-PROPERTIES joinOrder=fixed \n" + 
                "t1, t2 --DERBY-PROPERTIES joinStrategy=hash \n, t3 " +
                "where t1.hundred = t2.hundred " +
                "and t2.two = t3.two " +
                "and t1.hundred = t3.hundred").close();
        checkEstimatedRowCount(conn,3212015.00625);
        rtsp = SQLUtilities.getRuntimeStatisticsParser(s);
        assertTrue(rtsp.findString("Table Scan ResultSet for T1", 1));
        assertTrue(rtsp.findString("Bit set of columns fetched={0, 3}", 1));
        assertTrue(rtsp.findString("Hash Scan ResultSet for T2", 1));
        assertTrue(rtsp.findString("Bit set of columns fetched={1, 3}", 1));
        assertTrue(rtsp.findString("Hash Scan ResultSet for T3", 1));
        assertTrue(rtsp.findString("Bit set of columns fetched={1, 3}", 1));
        
        // duplicate predicates; this time t1.hundred=?
        // will show up twice when t1 is optimized at the end
        // selectivity should be same as above
        s.executeQuery("select t1.id from " +
                "--DERBY-PROPERTIES joinOrder=fixed \n" + 
                "t2, t3, t1 " +
                "where t1.hundred = t2.hundred " +
                "and t2.two = t3.two " +
                "and t1.hundred = t3.hundred").close();
        checkEstimatedRowCount(conn,3212015.00625);
        rtsp = SQLUtilities.getRuntimeStatisticsParser(s);
        assertTrue(rtsp.findString("Table Scan ResultSet for T2", 1));
        assertTrue(rtsp.findString("Bit set of columns fetched={1, 3}", 1));
        assertTrue(rtsp.findString("Hash Scan ResultSet for T3", 1));
        assertTrue(rtsp.findString("Bit set of columns fetched={1, 3}", 1));
        assertTrue(rtsp.findString("Hash Scan ResultSet for T1", 1));
        assertTrue(rtsp.findString("Bit set of columns fetched={0, 3}", 1));
        
        // variations on above query; try different join strategies
        s.executeQuery("select t1.id from " +
                "--DERBY-PROPERTIES joinOrder=fixed \n" + 
                "t3, t2, t1 --DERBY-PROPERTIES joinStrategy=hash \n" +
                "where t1.hundred = t2.hundred " +
                "and t2.two = t3.two " +
                "and t1.hundred = t3.hundred").close();
        checkEstimatedRowCount(conn,3212015.00625);
        rtsp = SQLUtilities.getRuntimeStatisticsParser(s);
        assertTrue(rtsp.findString("Table Scan ResultSet for T3", 1));
        assertTrue(rtsp.findString("Bit set of columns fetched={1, 3}", 1));
        assertTrue(rtsp.findString("Hash Scan ResultSet for T2", 1));
        assertTrue(rtsp.findString("Bit set of columns fetched={1, 3}", 1));
        assertTrue(rtsp.findString("Hash Scan ResultSet for T1", 1));
        assertTrue(rtsp.findString("Bit set of columns fetched={0, 3}", 1));
        
        s.executeQuery("select t1.id from " +
                "--DERBY-PROPERTIES joinOrder=fixed \n" + 
                "t3, t2, t1 --DERBY-PROPERTIES joinStrategy=nestedLoop \n" +
                "where t1.hundred = t2.hundred " +
                "and t2.two = t3.two " +
                "and t1.hundred = t3.hundred").close();
        checkEstimatedRowCount(conn,3212015.00625);
        rtsp = SQLUtilities.getRuntimeStatisticsParser(s);
        assertTrue(rtsp.findString("Table Scan ResultSet for T3", 1));
        assertTrue(rtsp.findString("Bit set of columns fetched={1, 3}", 1));
        assertTrue(rtsp.findString("Hash Scan ResultSet for T2", 1));
        assertTrue(rtsp.findString("Bit set of columns fetched={1, 3}", 1));
        assertTrue(rtsp.usedSpecificIndexForIndexScan("T1", "T1_HUNDRED"));
        assertTrue(rtsp.findString("Bit set of columns fetched=All", 1));
        
        s.executeQuery("select t1.id from " +
                "--DERBY-PROPERTIES joinOrder=fixed \n" + 
                "t2, t3 --DERBY-PROPERTIES joinStrategy=nestedLoop \n, t1 " +
                "where t1.hundred = t2.hundred " +
                "and t2.two = t3.two " +
                "and t1.hundred = t3.hundred").close();
        checkEstimatedRowCount(conn,3212015.00625);
        rtsp = SQLUtilities.getRuntimeStatisticsParser(s);
        assertTrue(rtsp.findString("Table Scan ResultSet for T2", 1));
        assertTrue(rtsp.findString("Bit set of columns fetched={1, 3}", 1));
        assertTrue(rtsp.usedSpecificIndexForIndexScan("T3", "T3_HUNDRED"));
        assertTrue(rtsp.findString("Bit set of columns fetched=All", 1));
        assertTrue(rtsp.findString("Hash Scan ResultSet for T1", 1));
        assertTrue(rtsp.findString("Bit set of columns fetched={0, 3}", 1));
        
        s.executeQuery("select t1.id from " +
                "--DERBY-PROPERTIES joinOrder=fixed \n" + 
                "t3, t2 --DERBY-PROPERTIES joinStrategy=hash \n, t1 " +
                "where t1.hundred = t2.hundred " +
                "and t2.two = t3.two " +
                "and t1.hundred = t3.hundred").close();
        checkEstimatedRowCount(conn,3212015.00625);
        rtsp = SQLUtilities.getRuntimeStatisticsParser(s);
        assertTrue(rtsp.findString("Table Scan ResultSet for T3", 1));
        assertTrue(rtsp.findString("Bit set of columns fetched={1, 3}", 1));
        assertTrue(rtsp.findString("Hash Scan ResultSet for T2", 1));
        assertTrue(rtsp.findString("Bit set of columns fetched={1, 3}", 1));
        assertTrue(rtsp.findString("Hash Scan ResultSet for T1", 1));
        assertTrue(rtsp.findString("Bit set of columns fetched={0, 3}", 1));
        
        // some more variations on the above theme
        // some constant predicates thrown in.
        // remember hundred is a char column
        // -- for some reason if you give the constant 
        // as a numeric argument it doesn't recognize that 
        // as a constant start/stop value for the index 
        // The error is that the types must be comparable.
        s.executeQuery("select t1.id from " +
                "--DERBY-PROPERTIES joinOrder=fixed \n" + 
                "t2, t3, t1 " +
                "where t1.hundred = t2.hundred " +
                "and t2.two = t3.two " +
                "and t1.hundred = t3.hundred " +
                "and t1.hundred='1'").close();
        checkEstimatedRowCount(conn,30458.025);
        rtsp = SQLUtilities.getRuntimeStatisticsParser(s);
        assertTrue(rtsp.usedSpecificIndexForIndexScan("T2", "T2_HUNDRED"));
        assertTrue(rtsp.findString("Bit set of columns fetched=All", 1));
        assertTrue(rtsp.findString("Hash Scan ResultSet for T3", 1));
        assertTrue(rtsp.findString("Bit set of columns fetched={1, 3}", 1));
        assertTrue(rtsp.usedSpecificIndexForIndexScan("T1", "T1_HUNDRED"));
        assertTrue(rtsp.findString("Bit set of columns fetched=All", 1));
        
        // we have t1.100=t2.100 and t1.100=t3.100, so 
        // t2.100=t3.100 is redundant. 
        // row count shouldn't factor in the redundant predicate.
        // row count should be 3200000.0
        s.executeQuery("select t1.id from " +
                "--DERBY-PROPERTIES joinOrder=fixed \n" + 
                "t2, t3, t1 " +
                "where t1.hundred = t2.hundred " +
                "and t2.two = t3.two " +
                "and t1.hundred = t3.hundred " +
                "and t2.hundred = t3.hundred").close();
        checkEstimatedRowCount(conn,3212015.00625);
        rtsp = SQLUtilities.getRuntimeStatisticsParser(s);
        assertTrue(rtsp.findString("Table Scan ResultSet for T2", 1));
        assertTrue(rtsp.findString("Bit set of columns fetched={1, 3}", 1));
        assertTrue(rtsp.findString("Hash Scan ResultSet for T3", 1));
        assertTrue(rtsp.findString("Bit set of columns fetched={1, 3}", 1));
        assertTrue(rtsp.findString("Hash Scan ResultSet for T1", 1));
        assertTrue(rtsp.findString("Bit set of columns fetched={0, 3}", 1));
        
        // slightly different join predicates-- use composite stats.
        // t1 x t2            --> 16 * 10.4.
        //         x t3       --> 16 * 10.4 * 4000 * 1/40 = 16*10.6
        s.executeQuery("select t1.id from " +
                "--DERBY-PROPERTIES joinOrder=fixed \n" + 
                "t2, t3, t1 " +
                "where t1.hundred = t2.hundred " +
                "and t2.two = t3.two " +
                "and t2.twenty = t3.twenty").close();
        checkEstimatedRowCount(conn,1.606007503125E7);
        rtsp = SQLUtilities.getRuntimeStatisticsParser(s);
        assertTrue(rtsp.findString("Table Scan ResultSet for T2", 1));
        assertTrue(rtsp.findString("Bit set of columns fetched={1, 2, 3}", 1));
        assertTrue(rtsp.findString(
                "Hash Scan ResultSet for T3 using index T3_TWO_TWENTY", 1));
        assertTrue(rtsp.findString("Bit set of columns fetched={0, 1}", 1));
        assertTrue(rtsp.findString("Hash Scan ResultSet for T1", 1));
        assertTrue(rtsp.findString("Bit set of columns fetched={0, 3}", 1));
        
        // same as above but muck around with join order.
        s.executeQuery("select t1.id from " +
                "--DERBY-PROPERTIES joinOrder=fixed \n" + 
                "t1, t2, t3 " +
                "where t1.hundred = t2.hundred " +
                "and t2.two = t3.two " +
                "and t2.twenty = t3.twenty").close();
        checkEstimatedRowCount(conn,1.606007503125E7);
        rtsp = SQLUtilities.getRuntimeStatisticsParser(s);
        assertTrue(rtsp.findString("Table Scan ResultSet for T1", 1));
        assertTrue(rtsp.findString("Bit set of columns fetched={0, 3}", 1));
        assertTrue(rtsp.findString("Hash Scan ResultSet for T2", 1));
        assertTrue(rtsp.findString("Bit set of columns fetched={1, 2, 3}", 1));
        assertTrue(rtsp.findString(
                "Hash Scan ResultSet for T3 using index T3_TWO_TWENTY", 1));
        assertTrue(rtsp.findString("Bit set of columns fetched={0, 1}", 1));
        
        s.executeQuery("select t1.id from " +
                "--DERBY-PROPERTIES joinOrder=fixed \n" + 
                "t2, t1, t3 " +
                "where t1.hundred = t2.hundred " +
                "and t2.two = t3.two " +
                "and t2.twenty = t3.twenty").close();
        checkEstimatedRowCount(conn,1.606007503125E7);
        rtsp = SQLUtilities.getRuntimeStatisticsParser(s);
        assertTrue(rtsp.findString("Table Scan ResultSet for T2", 1));
        assertTrue(rtsp.findString("Bit set of columns fetched={1, 2, 3}", 1));
        assertTrue(rtsp.findString("Hash Scan ResultSet for T1", 1));
        assertTrue(rtsp.findString("Bit set of columns fetched={0, 3}", 1));
        assertTrue(rtsp.findString(
                "Hash Scan ResultSet for T3 using index T3_TWO_TWENTY", 1));
        assertTrue(rtsp.findString("Bit set of columns fetched={0, 1}", 1));
        
        s.executeQuery("select t1.id from " +
                "--DERBY-PROPERTIES joinOrder=fixed \n" + 
                "t1, t3, t2 " +
                "where t1.hundred = t2.hundred " +
                "and t2.two = t3.two " +
                "and t2.twenty = t3.twenty").close();
        checkEstimatedRowCount(conn,1.606007503125E7);
        rtsp = SQLUtilities.getRuntimeStatisticsParser(s);
        assertTrue(rtsp.findString("Table Scan ResultSet for T1", 1));
        assertTrue(rtsp.findString("Bit set of columns fetched={0, 3}", 1));
        assertTrue(rtsp.usedSpecificIndexForIndexScan("T3", "T3_TWO_TWENTY"));
        assertTrue(rtsp.findString("Bit set of columns fetched={0, 1}", 1));
        assertTrue(rtsp.findString("Hash Scan ResultSet for T2", 1));
        assertTrue(rtsp.findString("Bit set of columns fetched={1, 2, 3}", 1));
        
        s.executeQuery("select t1.id from " +
                "--DERBY-PROPERTIES joinOrder=fixed \n" + 
                "t3, t2, t1 " +
                "where t1.hundred = t2.hundred " +
                "and t2.two = t3.two " +
                "and t2.twenty = t3.twenty").close();
        checkEstimatedRowCount(conn,1.606007503125E7);
        rtsp = SQLUtilities.getRuntimeStatisticsParser(s);
        assertTrue(rtsp.usedSpecificIndexForIndexScan("T3", "T3_TWO_TWENTY"));
        assertTrue(rtsp.findString("Bit set of columns fetched={0, 1}", 1));
        assertTrue(rtsp.findString("Hash Scan ResultSet for T2", 1));
        assertTrue(rtsp.findString("Bit set of columns fetched={1, 2, 3}", 1));
        assertTrue(rtsp.findString("Hash Scan ResultSet for T1", 1));
        assertTrue(rtsp.findString("Bit set of columns fetched={0, 3}", 1));
        
        s.executeQuery("select t1.id from " +
                "--DERBY-PROPERTIES joinOrder=fixed \n" + 
                "t3, t1, t2 " +
                "where t1.hundred = t2.hundred " +
                "and t2.two = t3.two " +
                "and t2.twenty = t3.twenty").close();
        checkEstimatedRowCount(conn,1.606007503125E7);
        rtsp = SQLUtilities.getRuntimeStatisticsParser(s);
        assertTrue(rtsp.usedSpecificIndexForIndexScan("T3", "T3_TWO_TWENTY"));
        assertTrue(rtsp.findString("Bit set of columns fetched={0, 1}", 1));
        assertTrue(rtsp.findString("Table Scan ResultSet for T1", 1));
        assertTrue(rtsp.findString("Bit set of columns fetched={0, 3}", 1));
        assertTrue(rtsp.findString("Hash Scan ResultSet for T2", 1));
        assertTrue(rtsp.findString("Bit set of columns fetched={1, 2, 3}", 1));
        
        // and just for fun, what would we have gotten without statistics.
        s.executeQuery("select t1.id from " +
                "--DERBY-PROPERTIES useStatistics=false, joinOrder=fixed \n" + 
                "t3, t1, t2 " +
                "where t1.hundred = t2.hundred " +
                "and t2.two = t3.two " +
                "and t2.twenty = t3.twenty").close();
        checkEstimatedRowCount(conn,6.4240300125000015E7);
        rtsp = SQLUtilities.getRuntimeStatisticsParser(s);
        assertTrue(rtsp.usedSpecificIndexForIndexScan("T3", "T3_TWO_TWENTY"));
        assertTrue(rtsp.findString("Bit set of columns fetched={0, 1}", 1));
        assertTrue(rtsp.findString("Table Scan ResultSet for T1", 1));
        assertTrue(rtsp.findString("Bit set of columns fetched={0, 3}", 1));
        assertTrue(rtsp.findString("Hash Scan ResultSet for T2", 1));
        assertTrue(rtsp.findString("Bit set of columns fetched={1, 2, 3}", 1));
    }
    
    public void testScratch() throws SQLException {
        // make sure we do a good job of stats on 1/3
        setAutoCommit(false);
        Connection conn = getConnection();
        Statement s = createStatement();
        
        s.executeUpdate("CALL SYSCS_UTIL.SYSCS_UPDATE_STATISTICS" +
                "('APP','T1',NULL)");
        s.executeUpdate("CALL SYSCS_UTIL.SYSCS_UPDATE_STATISTICS" +
                "('APP','SCRATCH_TABLE',NULL)");
        s.executeUpdate("call SYSCS_UTIL.SYSCS_SET_RUNTIMESTATISTICS(1)");
        
        // Note: The original test did the first query *after* the following:
        // since the statistics (rowEstimates) are not precise, force a 
        // checkpoint to force out all the row counts to the container header,
        // and for good measure do a count which will update the row counts 
        // exactly.
        // s.executeUpdate("CALL SYSCS_UTIL.SYSCS_CHECKPOINT_DATABASE()");
        // But if that's executed, the estimatedRowCount becomes: 2582648.45
        // Without the checkpoint, the following select counts are unnecessary.
        // assertTableRowCount("T1", 4000);
        // assertTableRowCount("SCRATCH_TABLE", 4000);
        
        // preds are on columns 1 and 3
        // should use default stats for 100 (0.1) and 0.5 for two
        
        // 16*10.6 * 5*10.-2 = 80*10.4
        
        s.executeQuery("select s.id from " +
                "--DERBY-PROPERTIES joinOrder=fixed \n" + 
                "t1, scratch_table s " +
                "where t1.two = s.two " +
                "and s.hundred = CAST(CHAR(t1.hundred) AS INTEGER)").close();
        checkEstimatedRowCount(conn,802001.25);
        RuntimeStatisticsParser rtsp = 
                SQLUtilities.getRuntimeStatisticsParser(s);
        assertTrue(rtsp.usedHashJoin());
        
        // preds are on column 2.
        // 0.1 -> 16*10.5
        s.executeQuery("select s.id from " +
                "--DERBY-PROPERTIES joinOrder=fixed \n" + 
                "t1, scratch_table s " +
                "where t1.twenty = s.twenty").close();
        checkEstimatedRowCount(conn,1604002.5);
        rtsp = SQLUtilities.getRuntimeStatisticsParser(s);
        assertTrue(rtsp.usedSpecificIndexForIndexScan("T1", "T1_TWO_TWENTY"));
        assertTrue(rtsp.usedHashJoin());
        
        // preds are on column 2,3.
        // 0.01 -> 16*10.4
        s.executeQuery("select s.id from " +
                "--DERBY-PROPERTIES joinOrder=fixed \n" + 
                "t1, scratch_table s " +
                "where t1.twenty = s.twenty " +
                "and s.hundred = CAST(CHAR(t1.hundred) AS INTEGER)").close();
        checkEstimatedRowCount(conn,160400.2500000);
        rtsp = SQLUtilities.getRuntimeStatisticsParser(s);
        assertTrue(rtsp.usedSpecificIndexForIndexScan("T1", "T1_TWENTY_HUNDRED"));
        assertTrue(rtsp.usedHashJoin());
    }
    
    public void testStatMatcher() throws SQLException {
        // test of statistics matcher algorithm; make sure that we choose the
        // best statistics (the weight stuff in predicatelist)
        
        setAutoCommit(false);
        Connection conn = getConnection();
        Statement s = createStatement();
        
        s.executeUpdate("CALL SYSCS_UTIL.SYSCS_UPDATE_STATISTICS" +
                "('APP','T1',NULL)");
        s.executeUpdate("CALL SYSCS_UTIL.SYSCS_UPDATE_STATISTICS" +
                "('APP','T2',NULL)");
        s.executeUpdate("call SYSCS_UTIL.SYSCS_SET_RUNTIMESTATISTICS(1)");
        
        // 2,20,100
        s.executeQuery("select t1.id from t1, t2 " +
                "where t1.two = t2.two " +
                "and t1.twenty = t2.twenty " +
                "and t1.hundred = t2.hundred").close();
        checkEstimatedRowCount(conn,4010.00625);
        RuntimeStatisticsParser rtsp = 
                SQLUtilities.getRuntimeStatisticsParser(s);
        assertTrue(rtsp.usedHashJoin());
        
        // now muck around with the order of the predicates
        // 2,100,20
        s.executeQuery("select t1.id from t1, t2 " +
                "where t1.two = t2.two " +
                "and t1.hundred = t2.hundred " +
                "and t1.twenty = t2.twenty").close();
        checkEstimatedRowCount(conn,4010.00625);
        rtsp = SQLUtilities.getRuntimeStatisticsParser(s);
        assertTrue(rtsp.usedHashJoin());
        
        // 100,20,2
        s.executeQuery("select t1.id from t1, t2 " +
                "where t1.hundred = t2.hundred " +
                "and t1.twenty = t2.twenty " +
                "and t1.two = t2.two").close();
        checkEstimatedRowCount(conn,4010.00625);
        rtsp = SQLUtilities.getRuntimeStatisticsParser(s);
        assertTrue(rtsp.usedHashJoin());
        
        // 100,2,20
        s.executeQuery("select t1.id from t1, t2 " +
                "where t1.hundred = t2.hundred " +
                "and t1.two = t2.two " +
                "and t1.twenty = t2.twenty").close();
        checkEstimatedRowCount(conn,4010.00625);
        rtsp = SQLUtilities.getRuntimeStatisticsParser(s);
        assertTrue(rtsp.usedHashJoin());
        
        s.executeQuery("select t1.id from t1, t2 " +
                "where t1.twenty = t2.twenty " +
                "and t1.hundred = t2.hundred " +
                "and t1.two = t2.two").close();
        checkEstimatedRowCount(conn,4010.00625);
        rtsp = SQLUtilities.getRuntimeStatisticsParser(s);
        assertTrue(rtsp.usedHashJoin());
        
        s.executeQuery("select t1.id from t1, t2 " +
                "where t1.twenty = t2.twenty " +
                "and t1.two = t2.two " +
                "and t1.hundred = t2.hundred").close();
        checkEstimatedRowCount(conn,4010.00625);
        rtsp = SQLUtilities.getRuntimeStatisticsParser(s);
        assertTrue(rtsp.usedHashJoin());
    }
    
    // Beetle was the bug system for Cloudscape, the forerunner
    // of Derby. The bug report described a query that was hitting an Error:
    // XJ001: Java exception: '2 >=2: java.lang.ArrayIndexOutOfBoundsException
    // on a specific query; when running the same query with DERBY-PROPERTIES
    // useStatistics=false the same query worked correctly.
    // The fix is in org.apache.derby.impl.sql.compile.PredicateList
    // referencing beetle 4321.
    public void testBeetle4321() throws SQLException {
        // test of statistics matcher algorithm; make sure that we choose the
        // best statistics (the weight stuff in predicatelist)
        
        setAutoCommit(false);
        Connection conn = getConnection();
        Statement s = createStatement();
        
        s.executeUpdate("CALL SYSCS_UTIL.SYSCS_UPDATE_STATISTICS" +
                "('APP','COMPLEX',NULL)");
        s.executeUpdate("CALL SYSCS_UTIL.SYSCS_UPDATE_STATISTICS" +
                "('APP','TEMPLATE',NULL)");
        s.executeUpdate("call SYSCS_UTIL.SYSCS_SET_RUNTIMESTATISTICS(1)");
        
        s.executeQuery("select t10.two from complex t10, template t20 " +
                "where t10.two = 1 " +
                "and t10.hundred = 2 " +
                "and t10.a = 2 " +
                "and t10.b = 2").close();
        checkEstimatedRowCount(conn,7945.920000000);
        RuntimeStatisticsParser rtsp = 
                SQLUtilities.getRuntimeStatisticsParser(s);
        assertTrue(rtsp.usedSpecificIndexForIndexScan("COMPLEX", "COMPLEXIND"));
        assertTrue(rtsp.usedSpecificIndexForIndexScan("TEMPLATE", "TEMPLATE_TWO"));
    }
    
    public void testBasic() throws SQLException {
        // basic test for update statistics; make sure that statistics with
        // correct values are created and dropped and such.
        setAutoCommit(false);
        Statement s = createStatement();
        
        s.executeUpdate("CALL SYSCS_UTIL.SYSCS_SET_RUNTIMESTATISTICS(1)");
        
        // first on int, multi-column
        s.executeUpdate("create table tbasic1 " +
                "(c1 int generated always as identity, c2 int, c3 int)");
        for (int i=1; i<5 ; i++)
        {
            for (int j=1 ; j<3 ; j++)
            {
                for (int c=0; c<2 ; c++)
                    s.executeUpdate(
                        "insert into tbasic1 values " +
                        "(default, " + i + ", " + j + ")");
            }
        }
        
        // create index should automatically create stats. 
        s.executeUpdate("create index t1_c1c2 on tbasic1 (c1, c2)");
        ResultSet statsrs = s.executeQuery(
                "select indexname, stats, ncols from showstats " +
                "where indexname like 'T1_C1C2%' " +
                "order by indexname, stats, ncols");
        JDBC.assertFullResultSet(statsrs, new String[][] {
                {"T1_C1C2","numunique= 16 numrows= 16","1"},
                {"T1_C1C2","numunique= 16 numrows= 16","2"}});
        // index dropped stats should be dropped.
        s.executeUpdate("drop index t1_c1c2");
        statsrs = s.executeQuery(
                "select indexname, stats, ncols from showstats " +
                "where indexname like 'T1_C1C2%' order by indexname");
        JDBC.assertEmpty(statsrs);
        
        // second part of the test.
        // check a few extra types.
        s.executeUpdate("create table tbasic2 " +
                "(i int not null, " +
                "vc varchar(32) not null, " +
                "dt date, ch char(20), " +
                "constraint pk primary key (i, vc))");
        s.executeUpdate("create index tbasic2_i on tbasic2(i)");
        s.executeUpdate("create index tbasic2_ch_dt on tbasic2(ch, dt)");
        s.executeUpdate("create index tbasic2_dt_vc on tbasic2(dt, vc)");
        // do normal inserts. 
        s.executeUpdate(
                "insert into tbasic2 values (1, 'one', '2001-01-01', 'one')");
        s.executeUpdate(
                "insert into tbasic2 values (2, 'two', '2001-01-02', 'two')");
        s.executeUpdate(
                "insert into tbasic2 values (3, 'three', '2001-01-03', 'three')");
        s.executeUpdate(
                "insert into tbasic2 values (1, 'two', '2001-01-02', 'one')");
        s.executeUpdate(
                "insert into tbasic2 values (1, 'three', '2001-01-03', 'one')");
        s.executeUpdate(
                "insert into tbasic2 values (2, 'one', '2001-01-01', 'two')");
        
        // figure out the name of the primary key's backing index
        statsrs = s.executeQuery(
                "select conglomeratename from sys.sysconglomerates " +
                "where conglomeratename like 'SQL%'");
        statsrs.next();
        String backIndName = statsrs.getString("conglomeratename");
        s.executeUpdate("CALL SYSCS_UTIL.SYSCS_UPDATE_STATISTICS" +
                "('APP','TBASIC2','" + backIndName + "')");
        statsrs = s.executeQuery(
                "select indexname, stats, ncols from showstats " +
                "where indexname like 'SQL%' " +
                "order by indexname, stats, ncols");
        JDBC.assertFullResultSet(statsrs, new String[][] {
                {backIndName,"numunique= 3 numrows= 6","1"},
                {backIndName,"numunique= 6 numrows= 6","2"}});
        
        s.executeUpdate("CALL SYSCS_UTIL.SYSCS_UPDATE_STATISTICS" +
                "('APP','TBASIC2','TBASIC2_I')");
        statsrs = s.executeQuery(
                "select indexname, stats, ncols from showstats " +
                "where indexname = 'TBASIC2_I' order by indexname");
        JDBC.assertFullResultSet(statsrs, new String[][] {
                {"TBASIC2_I","numunique= 3 numrows= 6","1"}});
        
        // do another insert then just updstat for whole table.
        s.executeUpdate(
                "insert into tbasic2 values(2, 'three', '2001-01-03', 'two')");
        s.executeUpdate("CALL SYSCS_UTIL.SYSCS_UPDATE_STATISTICS" +
                "('APP','TBASIC2',null)");
        
        // make sure that stats are correct.
        statsrs = s.executeQuery(
                "select indexname, stats, ncols from showstats " +
                "where indexname = 'TBASIC2_I' order by indexname");
        JDBC.assertFullResultSet(statsrs, new String[][] {
                {"TBASIC2_I","numunique= 3 numrows= 7","1"}});
        statsrs = s.executeQuery(
                "select count(*) from (select distinct i from tbasic2) t");
        JDBC.assertFullResultSet(statsrs, new String[][] {{"3"}});
        
        statsrs = s.executeQuery(
                "select indexname, stats, ncols from showstats " +
                "where indexname = 'TBASIC2_CH_DT' order by indexname, stats");
        JDBC.assertFullResultSet(statsrs, new String[][] {
                {"TBASIC2_CH_DT","numunique= 3 numrows= 7","1"},
                {"TBASIC2_CH_DT","numunique= 7 numrows= 7","2"}});
        
        statsrs = s.executeQuery(
                "select count(*) from (select distinct ch from tbasic2) t");
        JDBC.assertFullResultSet(statsrs, new String[][] {{"3"}});
        statsrs = s.executeQuery(
                "select count(*) from (select distinct ch, dt from tbasic2) t");
        JDBC.assertFullResultSet(statsrs, new String[][] {{"7"}});
        statsrs = s.executeQuery(
                "select indexname, stats, ncols from showstats " +
                "where indexname = 'TBASIC2_DT_VC' " +
                "order by indexname, stats, ncols");
        JDBC.assertFullResultSet(statsrs, new String[][] {
                {"TBASIC2_DT_VC","numunique= 3 numrows= 7","1"},
                {"TBASIC2_DT_VC","numunique= 3 numrows= 7","2"}});
        statsrs = s.executeQuery(
                "select count(*) from (select distinct dt from tbasic2) t");
        JDBC.assertFullResultSet(statsrs, new String[][] {{"3"}});
        statsrs = s.executeQuery(
                "select count(*) from (select distinct dt, vc from tbasic2) t");
        JDBC.assertFullResultSet(statsrs, new String[][] {{"3"}});
        statsrs = s.executeQuery(
                "select stats, ncols from showstats " +
                "where indexname like 'SQL%' order by stats, ncols");
        JDBC.assertFullResultSet(statsrs, new String[][] {
                {"numunique= 3 numrows= 7","1"},
                {"numunique= 7 numrows= 7","2"}});
        
        // delete everything from t2, do bulkinsert see what happens.
        assertUpdateCount(s, 7, "delete from tbasic2");
        
        // no material impact on stats
        // note; the test didn't actually confirm, here's the expected now
        statsrs = s.executeQuery(
                "select indexname, stats, ncols from showstats " +
                "where indexname like 'TBASIC2%' " +
                "order by indexname, stats, ncols");
        JDBC.assertFullResultSet(statsrs, new String[][] {
                {"TBASIC2_CH_DT","numunique= 3 numrows= 7","1"},
                {"TBASIC2_CH_DT","numunique= 7 numrows= 7","2"},
                {"TBASIC2_DT_VC","numunique= 3 numrows= 7","1"},
                {"TBASIC2_DT_VC","numunique= 3 numrows= 7","2"},
                {"TBASIC2_I","numunique= 3 numrows= 7","1"}});
        s.executeUpdate("CALL SYSCS_UTIL.SYSCS_UPDATE_STATISTICS" +
                "('APP','TBASIC2',null)");
        statsrs = s.executeQuery(
                "select indexname, stats, ncols from showstats " +
                "where indexname like 'TBASIC2%' " +
                "order by indexname, stats, ncols");
        JDBC.assertEmpty(statsrs);
        
        // Note: the original (Cloudscape) test did a 'bulkinsert'.
        // this is now only possible internally, and is used in
        // the SYSCS_IMPORT_DATA system procedures.
        // Possibly this test can be added onto by moving the
        // value intended to be inserted into a data file, and calling
        // SYSCS_UTIL.SYSCS_IMPORT_DATA using that.
        // The row inserted was:
        //        "values (2, 'one', '2001-01-01', 'two')");
        // subsequently, there was a bulk insert-replace, this is
        // no longer supported. We could update the row.
        // the replacement was of row:
        //        "(2, 'one', '2001-01-01', 'two'), " +
        // by
        //        "(1, 'one', '2001-01-01', 'two')");
        s.executeUpdate("drop table tbasic2");
        
        // various alter table operations to ensure correctness.
        // 1. add and drop constraint.
        s.executeUpdate("create table tbasic3 " +
                "(x int not null generated always as identity," +
                " y int not null, z int)");
        s.executeUpdate(
                "insert into tbasic3 (y,z) values " +
                "(1,1),(1,2),(1,3),(1,null),(2,1),(2,2),(2,3),(2,null)");
        // first alter table to add primary key;
        s.executeUpdate("alter table tbasic3 " +
                "add constraint pk_tbasic3 primary key (x,y)");
        statsrs = s.executeQuery(
                "select conglomeratename from sys.sysconglomerates " +
                "where conglomeratename like 'SQL%'");
        statsrs.next();
        backIndName = statsrs.getString("conglomeratename");
        statsrs = s.executeQuery(
                "select stats, ncols from showstats " +
                "where indexname like '" + backIndName + "' " +
                "order by stats, ncols");
        JDBC.assertFullResultSet(statsrs, new String[][] {
                {"numunique= 8 numrows= 8","1"},
                {"numunique= 8 numrows= 8","2"}});
        // now drop the constraint
        s.executeUpdate("alter table tbasic3 drop constraint pk_tbasic3");
        statsrs = s.executeQuery(
                "select stats, ncols from showstats " +
                "where indexname like '" + backIndName + "' " +
                "order by stats, ncols");
        JDBC.assertEmpty(statsrs);
        
        // try compress with tons of rows. you can never tell 
        // what a few extra pages can do :)
        for (int i=0; i<9 ; i++)
            s.executeUpdate("insert into tbasic3(y,z) select y,z from tbasic3");
        statsrs = s.executeQuery("select count(*) from tbasic3");
        JDBC.assertFullResultSet(statsrs, new String[][] {{"4096"}});
        s.executeUpdate("create index tbasic3_xy on tbasic3(x,y)");
        statsrs = s.executeQuery(
                "select indexname, stats, ncols from showstats " +
                "where indexname like 'TBASIC3_XY%' " +
                "order by indexname, stats, ncols");
        JDBC.assertFullResultSet(statsrs, new String[][] {
                {"TBASIC3_XY","numunique= 4096 numrows= 4096","1"},
                {"TBASIC3_XY","numunique= 4096 numrows= 4096","2"}});
        s.executeUpdate("delete from tbasic3 where z is null");
        s.executeUpdate("call SYSCS_UTIL.SYSCS_COMPRESS_TABLE" +
                "('APP', 'TBASIC3', 0)");
        statsrs = s.executeQuery(
                "select indexname, stats, ncols from showstats " +
                "where indexname like 'TBASIC3_XY%' " +
                "order by indexname, stats, ncols");
        JDBC.assertFullResultSet(statsrs, new String[][] {
                {"TBASIC3_XY","numunique= 3072 numrows= 3072","1"},
                {"TBASIC3_XY","numunique= 3072 numrows= 3072","2"}});
        s.executeUpdate("drop table tbasic3");
        
        s.executeUpdate("create table tbasic4 " +
                "(x int, y int, z int)");
        s.executeUpdate("insert into tbasic4 values (1,1,1)");
        s.executeUpdate("insert into tbasic4 values (1,2,1)");
        s.executeUpdate("insert into tbasic4 values (1,1,2)");
        
        s.executeUpdate("create index tbasic4_x on tbasic4(x)");
        s.executeUpdate("create index tbasic4_xy on tbasic4(x,y)");
        s.executeUpdate("create index tbasic4_yz on tbasic4(y,z)");
        statsrs = s.executeQuery(
                "select indexname, stats, ncols from showstats " +
                "where indexname like 'TBASIC4%' " +
                "order by indexname, stats, ncols");
        JDBC.assertFullResultSet(statsrs, new String[][] {
                {"TBASIC4_X","numunique= 1 numrows= 3","1"},
                {"TBASIC4_XY","numunique= 1 numrows= 3","1"},
                {"TBASIC4_XY","numunique= 2 numrows= 3","2"},
                {"TBASIC4_YZ","numunique= 2 numrows= 3","1"},
                {"TBASIC4_YZ","numunique= 3 numrows= 3","2"}});

        // if we drop column x, then stats for tbasic4_x should get dropped
        // index tbasic4_xy should get rebuilt to only be on y. so one of the
        // stats should be recreated. and tbasic4_yz shouldn remain in its
        // entirety.
        s.executeUpdate("alter table tbasic4 drop column x");
        statsrs = s.executeQuery(
                "select indexname, stats, ncols from showstats " +
                "where indexname like 'TBASIC4%' " +
                "order by indexname, stats, ncols");
        JDBC.assertFullResultSet(statsrs, new String[][] {
                {"TBASIC4_XY","numunique= 2 numrows= 3","1"},
                {"TBASIC4_YZ","numunique= 2 numrows= 3","1"},
                {"TBASIC4_YZ","numunique= 3 numrows= 3","2"}});
        s.executeUpdate("drop table tbasic4");
        
        // test re tbasic5 were intended to exercise Cloudscape's
        // stored prepared statements. This is not supported in Derby
        // it also does some drop statistics, but there are already 
        // other tests that do this. So on to tbasic6.
        
        s.executeUpdate("create table tbasic6 " +
                "(i int generated always as identity," +
                " j varchar(10))");
        s.executeUpdate("create index tbasic6_i on tbasic6(i)");
        s.executeUpdate("create index tbasic6_j on tbasic6(j)");
        s.executeUpdate("create index tbasic6_ji on tbasic6(j,i)");
        char[] alphabet = {'a','b','c','d','e','f','g','h','i'};
        for (int i=0; i<alphabet.length-1 ; i++)
            s.executeUpdate("insert into tbasic6 " +
                    "values (default, '" + alphabet[i] + "')");
        for (int i=0; i<alphabet.length ; i++)
            s.executeUpdate("insert into tbasic6 " +
                    "values (default, '" + alphabet[i] + "')");
        for (int i=0; i<alphabet.length-1 ; i++)
            s.executeUpdate("insert into tbasic6 " +
                    "values (default, '" + alphabet[i] + "')");
        s.executeUpdate("CALL SYSCS_UTIL.SYSCS_UPDATE_STATISTICS" +
                "('APP','TBASIC6','TBASIC6_J')");
        s.executeUpdate("CALL SYSCS_UTIL.SYSCS_UPDATE_STATISTICS" +
                "('APP','TBASIC6',NULL)");
        statsrs = s.executeQuery(
                "select indexname, stats, ncols from showstats " +
                "where indexname like 'TBASIC6%' " +
                "order by indexname, stats, ncols");
        JDBC.assertFullResultSet(statsrs, new String[][] {
                {"TBASIC6_I","numunique= 25 numrows= 25","1"},
                {"TBASIC6_J","numunique= 9 numrows= 25","1"},
                {"TBASIC6_JI","numunique= 25 numrows= 25","2"},
                {"TBASIC6_JI","numunique= 9 numrows= 25","1"}});

        s.executeUpdate("delete from TBASIC6");
        // make the 17th row the same as the 16th;
        // make sure when we switch to the next group fetch
        // we handle the case correctly.
        for (int i=0; i<17 ; i++)
            s.executeUpdate("insert into tbasic6 values (default, 'a')");
        s.executeUpdate("CALL SYSCS_UTIL.SYSCS_UPDATE_STATISTICS" +
                "('APP','TBASIC6',NULL)");
        statsrs = s.executeQuery(
                "select indexname, stats, ncols from showstats " +
                "where indexname like 'TBASIC6%' " +
                "order by indexname, stats, ncols");
        JDBC.assertFullResultSet(statsrs, new String[][] {
                {"TBASIC6_I","numunique= 17 numrows= 17","1"},
                {"TBASIC6_J","numunique= 1 numrows= 17","1"},
                {"TBASIC6_JI","numunique= 1 numrows= 17","1"},
                {"TBASIC6_JI","numunique= 17 numrows= 17","2"}});
        s.executeUpdate("drop table tbasic6");
        
        // table with no rows.
        s.executeUpdate("create table et (x int, y int)");
        s.executeUpdate("create index etx on et(x)");
        s.executeUpdate("create index ety on et(y)");
        s.executeUpdate("CALL SYSCS_UTIL.SYSCS_UPDATE_STATISTICS" +
                "('APP','ET','ETX')");
        s.executeUpdate("CALL SYSCS_UTIL.SYSCS_UPDATE_STATISTICS" +
                "('APP','ET',NULL)");
        statsrs = s.executeQuery(
                "select indexname, stats, ncols from showstats " +
                "where indexname like 'ET%' " +
                "order by indexname, stats, ncols");
        JDBC.assertEmpty(statsrs);
        s.executeUpdate("drop table et");
        
        // tests for nulls.
        s.executeUpdate("create table null_table (x int, y varchar(2))");
        s.executeUpdate("create index nt_x on null_table(x desc)");
        for (int i=1; i<4 ; i++)
            s.executeUpdate("insert into null_table " +
                    "values (" + i + ", '" + alphabet[i-1] + "')");
        for (int c=0; c<2 ; c++)
        {
            for (int i=1; i<4 ; i++)
                s.executeUpdate("insert into null_table " +
                        "values (null, '" + alphabet[i-1] + "')");
        }
        s.executeUpdate("CALL SYSCS_UTIL.SYSCS_UPDATE_STATISTICS" +
                "('APP','NULL_TABLE',NULL)");
        statsrs = s.executeQuery(
                "select indexname, stats, ncols from showstats " +
                "where indexname = 'NT_X' " +
                "order by indexname, stats, ncols");
        JDBC.assertFullResultSet(statsrs, new String[][] {
                {"NT_X","numunique= 9 numrows= 9","1"}});
        // try composite null keys (1,null) is unique from (1,null)
        // as is (null,1) from (null,1)
        s.executeUpdate("drop index nt_x");
        s.executeUpdate("create index nt_yx on null_table(y,x)");
        // the first key y has 3 unique values.
        // the second key y,x has 9 unique values because of nulls.
        statsrs = s.executeQuery(
                "select indexname, stats, ncols from showstats " +
                "where indexname = 'NT_YX' " +
                "order by indexname, stats, ncols");
        JDBC.assertFullResultSet(statsrs, new String[][] {
                {"NT_YX","numunique= 3 numrows= 9","1"},
                {"NT_YX","numunique= 9 numrows= 9","2"}});
    }
    
    // drop any tables created during testBasic
    protected void tearDown() throws Exception {
        Statement s = createStatement();
        try {
            s.execute("drop table tbasic1");
            s.execute("drop table tbasic2");
            s.execute("drop table tbasic3");
            s.execute("drop table tbasic4");
            s.execute("drop table tbasic6");
            s.execute("drop table et");
            s.execute("drop table null_table");
        } catch (SQLException sqle) {
            // if it doesn't work, never mind, we'll assume the
            // cleanDatabaseSetup will deal with it.
        }
        s.close();
        super.tearDown();
    }
}
