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

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

import java.sql.CallableStatement;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;

import java.text.Collator;
import java.text.RuleBasedCollator;

import java.util.Locale;
import java.util.Properties; 

import junit.framework.Assert;
import junit.framework.Test;
import junit.framework.TestSuite;

import org.apache.derbyTesting.junit.BaseJDBCTestCase;
import org.apache.derbyTesting.junit.Decorator;
import org.apache.derbyTesting.junit.JDBC;
import org.apache.derbyTesting.junit.SystemPropertyTestSetup;
import org.apache.derbyTesting.junit.SupportFilesSetup;
import org.apache.derbyTesting.junit.TestConfiguration;

/**
Junit test targeted at testing language based Collation.

Test the following with data that shows different ordering between default
collation and different language based collation:
T0: (DONE) Heap based compare using predicate pushing
T1: (DONE) Index based compare start/stop predicates on index
T2: (TODO) Index based compare using predicate pushing
T3: (DONE) order by on heap using in memory sorter
T4: (TODO) order by on heap using disk based sorter
T5: (TODO) system catalogs should not be collated
T6: (DONE) test like
T7: (TODO) test create conglomerate triggered by DiskHashtable code
T8: (TODO) test create conglomerate triggered by DataDictionaryImpl
T9: (TODO) test create conglomerate triggered by java/engine/org/apache/derby/impl/sql/conn/GenericLanguageConnectionContext.java
T10: (DONE) alter table compress with indexes
T11: (DONE) alter table drop column with indexes
T12: (DONE) alter table add column with index
T13: (DONE) bulk insert into empty table, with and without indexes
T14: (DONE) bulk insert replace, with and without indexes

T15: (TODO) java/engine/org/apache/derby/impl/sql/execute/MaterializedResultSet.java
T16: (TODO) /java/engine/org/apache/derby/impl/sql/execute/TemporaryRowHolderImpl.java
T17: (TODO) /java/engine/org/apache/derby/impl/store/access/PropertyConglomerate.java
T18: (TODO) upgrade tests - may be changes to upgrade suite rather than here.
T19: (TODO) recovery testing - may be old function harness changes as no one has
            suggested how to do this in junit harness.
T20: (TODO) For both a newly created 10.3 database and an upgraded 10.3 
            database, make sure that metadata continues to show the scale for 
            character datatypes as 0 (rather than the collation type value). 
            That is, test that the scale of the character datatypes is always
            0 and it didn't get impacted negatively by the overloading of scale
            field as collation type in TypeDescriptor. 
T21: (TODO) Testing with views
T22: (TODO) Testing with global temporary tables
T23: (TODO) Alter table testing. Two specific cases 1)add a new character column and 2)increase the length of an existing character
T24: (DONE) DERBY-2669 If no territory attribute is specified at create 
            database time, then create collated db based on default territory
            of Database.


<p>
NOTE: The prefix "ci_test" is used for tests that require a case insensitive
      collation order.

**/

public class CollationTest2 extends BaseJDBCTestCase 
{
    /**************************************************************************
     * Fields of the class
     **************************************************************************
     */


    /**
     * Set to get output if something in the test is failing and you want
     * more information about what was going on.
     **/
    private static final boolean    verbose_debug = false;

    private static final int    TEST_DEFAULT            = 0;
    private static final int    TEST_ENGLISH            = 1;
    private static final int    TEST_POLISH             = 2;
    private static final int    TEST_NORWAY             = 3;


    private static final String[] NAMES =
    {
        // Just Smith, Zebra, Acorn with alternate A,S and Z
        "Smith",
        "Zebra",
        "\u0104corn",
        "\u017Bebra",
        "Acorn",
        "\u015Amith",
        "aacorn"
    };

    private static final int[] DEFAULT_NAME_ORDER =
    {
        4, // Acorn
        0, // Smith
        1, // Zebra
        6, // aacorn
        2, // \u0104corn
        5, // \u015Amith
        3  // \u017Bebra
    };

    private static final int[] ENGLISH_NAME_ORDER =
    {
        6, // aacorn
        4, // Acorn
        2, // \u0104corn
        0, // Smith
        5, // \u015Amith
        1, // Zebra
        3  // \u017Bebra
    };

    private static final int[] POLISH_NAME_ORDER =
    {
        6, // aacorn
        4, // Acorn
        2, // \u0104corn
        0, // Smith
        5, // \u015Amith
        1, // Zebra
        3  // \u017Bebra
    };

    private static final int[] NORWAY_NAME_ORDER =
    {
        4, // Acorn
        2, // \u0104corn
        0, // Smith
        5, // \u015Amith
        1, // Zebra
        3, // \u017Bebra
        6  // aacorn
    };

    private static final int[][] EXPECTED_NAME_ORDER = 
    {
        DEFAULT_NAME_ORDER,
        ENGLISH_NAME_ORDER,
        POLISH_NAME_ORDER,
        NORWAY_NAME_ORDER
    };

    
    /**
     * set up LIKE test cases, configured for all languages by 
     * the TEST_* constants.
     * <p>
     * Insert all data to tested against into LIKE_NAMES. A customer table
     * will be filled with this data.
     * {p>
     * Insert test cases for like string into the LIKE_TEST_CASES, results
     * are expected only to return a single row.
     * <p>
     * Insert actual string expected back for each language, for each test
     * case in the {LANG}_LIKE_RESULT array.  Insert null if no match is
     * expected.
     * <p>
     * Current test tries all 4 datatypes, CHAR will blank pad making the
     * results different than the other datatypes if data is shorter than
     * type, thus a different set of LIKE clauses needs to be entered in the
     * LIKE_CHAR_TEST_CASES which should match the same results in a CHAR
     * field as does the corresponding LIKE_TEST_CASES test.  
     *
     **/
    private static final String[] LIKE_NAMES =
    {
        "Waagan",      // 0
        "Smith",       // 1
        "Zebra",       // 2
        "xcorn",       // 3
        "aBebra",      // 4
        "Acorn",       // 5
        "Amith",       // 6
        "aacorn",      // 7
        "xxxaa",       // 8
        "aaxxx",       // 9
        "yyyaa y",     // 10
    };

    private static final String[] LIKE_TEST_CASES = 
    {
        "Waagan",
        "W_gan",
        "aaxxx",
        "_xxx",
        "xxxaa",
        "xxx_",
        "xxx_%",
        "yyy_%"
    };
    private static final String[] LIKE_CHAR_TEST_CASES = 
    {
        "Waagan    ",
        "W_gan    ",
        "aaxxx%",
        "_xxx%",
        "xxx%",
        "xxx_ %",
        "xxx%",
        "yyy_%"
    };

    private static final int[] DEFAULT_LIKE_RESULT =
    {
        0,
        -1,
        9,
        -1,
        8,
        -1,
        8,
        10
    };
        
    private static final int[] ENGLISH_LIKE_RESULT =
    {
        0,
        -1,
        9,
        -1,
        8,
        -1,
        8,
        10
    };

    private static final int[] POLISH_LIKE_RESULT =
    {
        0,
        -1,
        9,
        -1,
        8,
        -1,
        8,
        10
    };

    private static final int[] NORWAY_LIKE_RESULT =
    {
        0,
        -1,
        9,
        -1,
        8,
        -1,
        8,
        10
    };

    private static final int[][] EXPECTED_LIKE_RESULTS = 
    {
        DEFAULT_LIKE_RESULT,
        ENGLISH_LIKE_RESULT,
        POLISH_LIKE_RESULT,
        NORWAY_LIKE_RESULT
    };


    /**************************************************************************
     * Constructors for This class:
     **************************************************************************
     */

    public CollationTest2(String name) 
    {
        super(name);
    }

    /**************************************************************************
     * Private/Protected setup/utility methods of This class:
     **************************************************************************
     */
    
    protected void initializeConnection(Connection conn) throws SQLException
    {
        conn.setAutoCommit(false);
    }


    /**
     * RESOLVE - unfinished LIKE test with dataset of all unicode characters
     **/
    private static final void printRuleBasedCollator()
    {
        // get en_US Collator rules
        RuleBasedCollator en_USCollator = 
            (RuleBasedCollator)Collator.getInstance(Locale.US);
        String en_rules = en_USCollator.getRules();

        System.out.println("ENGLISH RULES: " + en_rules);
        System.out.println("ENGLISH RULES: " + formatString(en_rules, true));
        System.out.println("ENGLISH RULES: " + formatString(en_rules, false));
    }

    /**
     * RESOLVE - unfinished LIKE test with dataset of all unicode characters
     **/
    private static final String formatString(
    String  str,
    boolean all)
    {
        // format it as \u0000(x)\u0001(x)...
        String ret_val = "";

        for (int i = 0; i < str.length(); i++)
        {
            char ch = str.charAt(i);

            if (!all && (ch <= 128))
            {
                ret_val += Character.toString(ch);
            }
            else
            {
                ret_val += 
                    "\\u" + Integer.toString(ch, 16) + 
                    "(" + Character.toString(ch) + ")";
            }
        }
        return(ret_val);
    }

    /**
     * RESOLVE - unfinished LIKE test with dataset of all unicode characters
     **/
    private final void formatLikeResults(
    Connection  conn,
    String      query)
        throws SQLException
    {
        Statement s     = conn.createStatement();
        ResultSet rs    = s.executeQuery(query);


        String    txt_str   = null;
        String    ucode_str = null;
        String    mixed_str = null;

        int       count     = 0;

        while (rs.next())
        {
            count++;

            if (count == 1)
            {
                txt_str   = "{";
                ucode_str = "{";
                mixed_str = "{";
            }
            else
            {
                txt_str   += ", ";
                ucode_str += ", ";
                mixed_str += ", ";
            }

            String ret_val = rs.getString(1);

            txt_str   += "\"" + ret_val + "\"";
            
            // string using \u0104 format for chars
            ucode_str += formatString(ret_val, true);

            mixed_str += "{\"" + formatString(ret_val, false) + "\"}";
        }

        if (count != 0)
        {
            txt_str   += "}";
            ucode_str += "}";
            mixed_str += "}";
        }

        System.out.println(
            "Query: " + query + 
            "\nnumber rows  :" + count     +
            "\nString Result:" + txt_str   +
            "\nUcode  Result:" + ucode_str +
            "\nmixed  Result:" + mixed_str);

        rs.close();
        s.close();
    }

    /**
     * RESOLVE - unfinished LIKE test with dataset of all unicode characters
     **/
    private final void printLikeResults(Connection conn)
        throws SQLException
    {
        /*
        RESOLVE-COMMENTED OUT

        for (int i = 0; i < LIKE_ALLVALS_TEST.length; i++)
        {
            if (verbose_debug)
            {
                System.out.println(
                    "Running like allvals test[" + i + "] = " + 
                    LIKE_ALLVALS_TEST[i]);
            }

            formatLikeResults(
                conn,
                "SELECT STR_VARCHAR FROM ALLVALS WHERE STR_VARCHAR LIKE " +
                    "'" + LIKE_ALLVALS_TEST[i] + "'");

            formatLikeResults(
                conn,
                "SELECT STR_LONGVARCHAR FROM ALLVALS WHERE STR_LONGVARCHAR LIKE " +
                    "'" + LIKE_ALLVALS_TEST[i] + "'");

            formatLikeResults(
                conn,
                "SELECT STR_CLOB FROM ALLVALS WHERE STR_CLOB LIKE " +
                    "'" + LIKE_ALLVALS_TEST[i] + "'");

            formatLikeResults(
                conn,
                "SELECT STR_CHAR FROM ALLVALS WHERE STR_CHAR LIKE " +
                    "'" + LIKE_ALLVALS_TEST[i] + "'");
        }
        */
    }

    private void checkLangBasedQuery(
    String      query, 
    String[][]  expectedResult,
    boolean     ordered) 
        throws SQLException 
    {
        Statement s  = createStatement();
        ResultSet rs = s.executeQuery(query);

        if (verbose_debug)
        {
            System.out.println("executed query: " + query);
        }

        if (expectedResult == null) //expecting empty resultset from the query
        {
            if (verbose_debug)
                System.out.println(
                    "executed query expecting no results: " + query);

            JDBC.assertEmpty(rs);
        }
        else
        {
            if (ordered)
            {
                if (verbose_debug)
                    System.out.println(
                        "executed query expecting ordered results: " + query);

                JDBC.assertFullResultSet(rs, expectedResult);
            }
            else
            {
                if (verbose_debug)
                    System.out.println(
                        "executed query expecting unordered results: " + query);

                JDBC.assertUnorderedResultSet(rs, expectedResult);
            }
        }
    }

    private void checkParamQuery(
    String      query, 
    String[]      param,
    int    paramNumber,
    String[][]  expectedResult, 
    boolean     ordered) 
        throws SQLException 
    {
        PreparedStatement   ps = prepareStatement(query);
        for (int i=0; i < paramNumber;i++)
        {
        	ps.setString(i+1, param[i]);
        }
        ResultSet           rs = ps.executeQuery();

        if (expectedResult == null) //expecting empty resultset from the query
        {
            JDBC.assertEmpty(rs);
        }
        else
        {
            if (ordered)
                JDBC.assertFullResultSet(rs,expectedResult);
            else
                JDBC.assertUnorderedResultSet(rs, expectedResult);
        }


        // re-execute it to test path through the cache
        for (int i=0; i < paramNumber;i++)
        {
        	ps.setString(i+1, param[i]);
        }
        rs = ps.executeQuery();

        if (expectedResult == null) //expecting empty resultset from the query
        {
            JDBC.assertEmpty(rs);
        }
        else
        {
            if (ordered)
                JDBC.assertFullResultSet(rs,expectedResult);
            else
                JDBC.assertUnorderedResultSet(rs, expectedResult);
        }
        commit();
    }

    /**
     * Perform export using SYSCS_UTIL.SYSCS_EXPORT_TABLE procedure.
     */
    protected void doExportTable(
    String      schemaName, 
    String      tableName, 
    String      fileName, 
    String      colDel , 
    String      charDel, 
    String      codeset) 
        throws SQLException 
    {
	//DERBY-2925: need to delete existing files first.
        SupportFilesSetup.deleteFile(fileName);

        CallableStatement ps = 
            prepareCall(
                "call SYSCS_UTIL.SYSCS_EXPORT_TABLE (? , ? , ? , ?, ? , ?)");
        ps.setString(1, schemaName);
        ps.setString(2, tableName);
        ps.setString(3, fileName);
        ps.setString(4, colDel);
        ps.setString(5, charDel);
        ps.setString(6, codeset);
        ps.executeUpdate();
        ps.close();
    }

    /**
     * Perform import using SYSCS_UTIL.SYSCS_IMPORT_TABLE procedure.
     */
    protected void doImportTable(
    String      schemaName, 
    String      tableName, 
    String      fileName, 
    String      colDel, 
    String      charDel, 
    String      codeset,
    int         replace) 
        throws SQLException 
    {
        CallableStatement ps = 
            prepareCall(
                "call SYSCS_UTIL.SYSCS_IMPORT_TABLE (?, ?, ?, ?, ?, ?, ?)");
        ps.setString(1, schemaName);
        ps.setString(2, tableName);
        ps.setString(3, fileName);
        ps.setString(4, colDel);
        ps.setString(5, charDel);
        ps.setString(6, codeset);
        ps.setInt(   7, replace);
        ps.executeUpdate();
        ps.close();
    }

    /**
     * Produce an expect row set given the order and asc/desc info.
     * <p>
     * Given the expected order of rows, the offset of first and last row
     * to return, and whether rows will be ascending or descending produce
     * a 2d expected row set.  Each row in the row set represents a row 
     * with 2 columns (ID, NAME) from the CUSTOMER table used throughout
     * this test.
     *
     * @param expected_order    Expected order of rows in this language.
     * @param start_offset      expect rows starting at 
     *                          expected_order[start_offset] up to and including
     *                          expected_order[stop_offset].
     * @param stop_offset       expect rows starting at 
     *                          expected_order[start_offset] up to and including
     *                          expected_order[stop_offset].
     * @param ascending_order   true if rows are in order, else rows are in
     *                          reverse order.
     **/
    private String[][] full_row_set(
    int[]   expected_order,
    int     start_offset,
    int     stop_offset,
    boolean ascending_order)
    {
        String[][] ret_order = null;

        int num_vals = stop_offset - start_offset + 1;

        if (num_vals > 0)
        {
            ret_order = new String[num_vals][2];

            if (ascending_order)
            {
                int dest = 0;
                for (int src = start_offset; src <= stop_offset; src++)
                {
                    ret_order[dest][0] = String.valueOf(expected_order[src]);
                    ret_order[dest][1] = NAMES[expected_order[src]];
                    dest++;
                }
            }
            else
            {
                // rows are expected in reverse order from what is passsed in,
                // so swap them to create the output expected result array.
                int dest = 0;
                for (int src = stop_offset; src >= start_offset; src--)
                {
                    ret_order[dest][0] = String.valueOf(expected_order[src]);
                    ret_order[dest][1] = NAMES[expected_order[src]];
                    dest++;
                }
            }
        }

        return(ret_order);
    }

    /**
     * Produce an expect row set given list and offset of row in list.
     * <p>
     * Given the list of rows and offset of the expected row in the list
     * produce a 2d expected row set.  If expected_row is -1 then no row
     * set is returned.  Each row in the row set represents a row 
     * with 2 columns (ID, NAME) from the CUSTOMER table used throughout
     * this test.
     *
     * @param expected_row      -1 if no expected row, else 
     *                          ret_list[expected_row] is single value expected.
     * @param ret_list          list of strings in data set.
     **/
    private String[][] full_row_single_value(
    int         expected_row,
    String[]    ret_list)
    {
        String[][] ret_order = null;

        if (expected_row != -1)
        {
            // if not -1 then exactly one row expected.
            ret_order = new String[1][2];
            ret_order[0][0] = String.valueOf(expected_row);
            ret_order[0][1] = ret_list[expected_row];
        }

        return(ret_order);
    }

    private boolean isDatabaseBasicCollation()
        throws SQLException
    {       
        return "UCS_BASIC".equals(getDatabaseProperty("derby.database.collation"));
    }


    /**************************************************************************
     * Set up and clean up routines.
     **************************************************************************
     */

    private void setUpTable() throws SQLException 
    {
        Statement s = createStatement();
        s.execute(
            "CREATE TABLE CUSTOMER(" +
                "D1 CHAR(200), D2 CHAR(200), D3 CHAR(200), D4 INT, " + 
                "ID INT, NAME VARCHAR(40), NAME2 VARCHAR(40))");

        PreparedStatement ps = 
            prepareStatement("INSERT INTO CUSTOMER VALUES(?,?,?,?,?,?,?)");

        for (int i = 0; i < NAMES.length; i++)
        {
            ps.setString(1, NAMES[i]);
            ps.setString(2, NAMES[i]);
            ps.setString(3, NAMES[i]);
            ps.setInt(   4, i);
            ps.setInt(   5, i);
            ps.setString(6, NAMES[i]);
            ps.setString(7, NAMES[i]);
            ps.executeUpdate();
        }

        s.execute(
        		"CREATE TABLE EMPTY_TABLE (NAME VARCHAR(40))");
        s.execute(
        		"CREATE INDEX EMPTY_TABLE_IDX ON EMPTY_TABLE(NAME)");
        commit();
    }

    private void setUpLikeTable() throws SQLException 
    {
        Statement s = createStatement();
        s.execute(
            "CREATE TABLE CUSTOMER ("              +
                "NAME_CHAR          CHAR(10), "    +
                "NAME_VARCHAR       VARCHAR(40),"  +
                "NAME_LONGVARCHAR   LONG VARCHAR," +
                "NAME_CLOB          CLOB,"         +
                "ID                 INT)");

        PreparedStatement ps = 
            prepareStatement("INSERT INTO CUSTOMER VALUES(?,?,?,?,?)");

        for (int i = 0; i < LIKE_NAMES.length; i++)
        {
            ps.setString(1, LIKE_NAMES[i]);
            ps.setString(2, LIKE_NAMES[i]);
            ps.setString(3, LIKE_NAMES[i]);
            ps.setString(4, LIKE_NAMES[i]);
            ps.setInt(   5, i);
            ps.executeUpdate();
        }

        ps.close();
        s.close();
        commit();
    }

    
    /**
     * RESOLVE - unfinished LIKE test with dataset of all unicode characters
     **/
    private void setUpALLVALS(Connection conn) 
        throws SQLException 
    {
        Statement s = conn.createStatement();

        s.execute(
            "CREATE TABLE ALLVALS ("              +
                "STR_CHAR          CHAR(3), "    +
                "STR_VARCHAR       VARCHAR(40),"  +
                "STR_LONGVARCHAR   LONG VARCHAR," +
                "STR_CLOB          CLOB,"         +
                "ID                INT)");

        PreparedStatement ps = 
            conn.prepareStatement("INSERT INTO ALLVALS VALUES(?,?,?,?,?)");

        char[]  single_char = new char[1];
        char[]  leading_b   = new char[2];
        char[]  trailing_b  = new char[2];
        char[]  middle_b    = new char[3];

        leading_b[0]                        = 'b';
        trailing_b[trailing_b.length - 1]   = 'b';
        middle_b[1]                         = 'b';

        int max_char = (int) Character.MAX_VALUE;

        long before_load_ms = System.currentTimeMillis();

        for (int i = Character.MIN_VALUE; i <= max_char; i++)
        {
            // insert a row with string value of a single unicode char
            single_char[0] = (char) i;
            String  str_val = String.valueOf(single_char);

            ps.setString(1, str_val);
            ps.setString(2, str_val);
            ps.setString(3, str_val);
            ps.setString(4, str_val);
            ps.setInt(   5, i);
            ps.executeUpdate();

            // insert a row with 'b' followed by unicode value followed by 'b'
            leading_b[1] = (char) i;
            str_val = String.valueOf(leading_b);

            ps.setString(1, str_val);
            ps.setString(2, str_val);
            ps.setString(3, str_val);
            ps.setString(4, str_val);
            ps.setInt(   5, i);
            ps.executeUpdate();

            // insert a row with unicode value followed by 'b' 
            trailing_b[0] = (char) i;
            str_val = String.valueOf(trailing_b);

            ps.setString(1, str_val);
            ps.setString(2, str_val);
            ps.setString(3, str_val);
            ps.setString(4, str_val);
            ps.setInt(   5, i);
            ps.executeUpdate();

            // insert a row with unicode value followed by 'b' 
            // followed by unicode value
            middle_b[0] = (char) i;
            middle_b[2] = (char) i;
            str_val = String.valueOf(middle_b);

            ps.setString(1, str_val);
            ps.setString(2, str_val);
            ps.setString(3, str_val);
            ps.setString(4, str_val);
            ps.setInt(   5, i);
            ps.executeUpdate();
        }

        long after_load_ms = System.currentTimeMillis();

        conn.commit();

        long after_commit_ms = System.currentTimeMillis();

        if (verbose_debug)
        {

            System.out.println("Loaded and committed ALLVALS table:");
            System.out.println(
                "load time = "   + (after_load_ms   - before_load_ms) +
                "commit time = " + (after_commit_ms - after_load_ms));
        }

        ps.close();
        s.close();
    }

    /**************************************************************************
     * run*() tests, called from the actual test*() tests.
     **************************************************************************
     */


    /**
     * Test simple call to DatabaseMetaData.getColumns()
     * <p>
     * This test is the same form of the getColumns() call that 
     * the IMPORT and EXPORT system procedures depend on. 
     * Currently on ibm and sun 1.4.2 jvm's this test fails.
     **/
    private void runDERBY_2703(int db_index)
        throws SQLException
    {
        setUpTable();

        ResultSet rs = 
            getConnection().getMetaData().getColumns(null, "APP", "CUSTOMER", "%");
        
        int rowCount = JDBC.assertDrainResults(rs);

        Assert.assertTrue("catch bug where no rows are returned.", rowCount > 0);

        dropTable();
    }

    /**
     * Tests that DERBY-5367 is fixed, a bug where updating the index in a
     * database with a case insensitive collation resulted in data corruption.
     * <p>
     * The bug tested is where a deleted row with an incorrect key value in
     * the index is undeleted as an optimized insert. In this case it was
     * caused by the a case insensitive collation order, but other collation
     * rules could cause this to happen as well.
     */
    public void ci_testDerby5367()
            throws SQLException {
        assertFalse(isDatabaseBasicCollation());
        setAutoCommit(true);
        String TABLE = "DERBY_5367";
        Statement stmt = createStatement();
        stmt.executeUpdate("create table " + TABLE + "(" +
                "VAL varchar(10) not null unique)");
        
        // Run first time when the congloms were newly created.
        runDerby5367TestCode(TABLE);

        // Shut down the database, reboot. This will trigger the code to
        // read the congloms from disk.
        TestConfiguration.getCurrent().shutdownDatabase();
        getConnection();

        // Run second time, read congloms from disk.
        runDerby5367TestCode(TABLE);
        dropTable(TABLE);
    }

    /** Runs the core code for the DERBY-5367 test. */
    private void runDerby5367TestCode(String table)
            throws SQLException {
        PreparedStatement sel = prepareStatement("select val from " + table +
                " where val = 'Test'");
        PreparedStatement ins = prepareStatement("insert into " + table +
                " values ?");
        ins.setString(1, "Test");
        ins.executeUpdate();
        JDBC.assertFullResultSet(sel.executeQuery(), new String[][] {{"Test"}});
        Statement stmt = createStatement();
        stmt.executeUpdate("delete from " + table + " where val = 'Test'");
        ins.setString(1, "test");
        ins.executeUpdate();
        JDBC.assertFullResultSet(sel.executeQuery(), new String[][] {{"test"}});
        stmt.executeUpdate("delete from " + table);
    }

    /**************************************************************************
     * Private/Protected tests of This class:
     **************************************************************************
     */

    /**
     * Check simple boolean compare of string constant to column value.
     * <p>
     * Check <, <=, =, >=, > of constant to column, ie. of the form
     *     select * from table where col boolean constant
     *
     *
     * @throws SQLException
     **/
    private void checkSimpleCompare(
    int[]       expected_order)
        throws SQLException
    {
        // loop through all the rows using each as the descriminator, this
        // gives us low, high and middle special cases.  Expect the number
        // of rows for this test case to be low.
        for (int i = 0; i < expected_order.length; i++)
        {
            // '<' test
            checkLangBasedQuery(
                "SELECT ID, NAME FROM CUSTOMER where NAME < '" + 
                    NAMES[expected_order[i]] + "' ORDER BY NAME",
                full_row_set(
                    expected_order, 
                    0, 
                    i - 1,
                    true),
                true);

            // '<=' test
            checkLangBasedQuery(
                "SELECT ID, NAME FROM CUSTOMER where NAME <= '" + 
                    NAMES[expected_order[i]] + "' ORDER BY NAME",
                full_row_set(
                    expected_order, 
                    0, 
                    i,
                    true),
                true);

            // '=' test
            checkLangBasedQuery(
                "SELECT ID, NAME FROM CUSTOMER where NAME = '" + 
                    NAMES[expected_order[i]] + "' ORDER BY NAME",
                full_row_set(
                    expected_order, 
                    i, 
                    i,
                    true),
                true);

            // '>=' test
            checkLangBasedQuery(
                "SELECT ID, NAME FROM CUSTOMER where NAME >= '" + 
                    NAMES[expected_order[i]] + "' ORDER BY NAME",
                full_row_set(
                    expected_order, 
                    i, 
                    expected_order.length - 1,
                    true),
                true);


            // '>' test
            checkLangBasedQuery( 
                "SELECT ID, NAME FROM CUSTOMER where NAME > '" + 
                    NAMES[expected_order[i]] + "' ORDER BY NAME",
                full_row_set(
                    expected_order, 
                    i + 1, 
                    expected_order.length - 1,
                    true),
                true);

            // now check prepared query

            // '<' test
            checkParamQuery(
                "SELECT ID, NAME FROM CUSTOMER where NAME < ? ORDER BY NAME",
                new String[] {NAMES[expected_order[i]]},
                1,
                full_row_set(
                    expected_order, 
                    0, 
                    i - 1,
                    true),
                true);

            // '<=' test
            checkParamQuery(
                "SELECT ID, NAME FROM CUSTOMER where NAME <= ? ORDER BY NAME",
                new String[] {NAMES[expected_order[i]]},
                1,
                full_row_set(
                    expected_order, 
                    0, 
                    i,
                    true),
                true);

            // '=' test
            checkParamQuery(
                "SELECT ID, NAME FROM CUSTOMER where NAME = ? ORDER BY NAME",
                new String[] {NAMES[expected_order[i]]},
                1,
                full_row_set(
                    expected_order, 
                    i, 
                    i,
                    true),
                true);

            // '>=' test
            checkParamQuery(
                "SELECT ID, NAME FROM CUSTOMER where NAME >= ? ORDER BY NAME",
                new String[] {NAMES[expected_order[i]]},
                1,
                full_row_set(
                    expected_order, 
                    i, 
                    expected_order.length - 1,
                    true),
                true);

            // '>' test
            checkParamQuery(
                "SELECT ID, NAME FROM CUSTOMER where NAME > ? ORDER BY NAME",
                new String[] {NAMES[expected_order[i]]},
                1,
                full_row_set(
                    expected_order, 
                    i + 1, 
                    expected_order.length - 1,
                    true),
                true);
        }
    }

    /**
     * Check simple boolean compare of string constant to column value.
     * <p>
     * Check <, <=, =, >=, > of constant to column, ie. of the form
     *     select * from table where col boolean constant
     *
     *
     * @throws SQLException
     **/
    private void checkTwoPersistentCompare(
    int[]       expected_order)
        throws SQLException
    {
        Statement s  = createStatement();

        commit();
        s.execute(
            "ALTER TABLE CUSTOMER ADD COLUMN TWO_CHECK_CHAR CHAR(40)");
        s.execute(
            "ALTER TABLE CUSTOMER ADD COLUMN TWO_CHECK_VARCHAR VARCHAR(400)");

        // Set CHAR field to be third item im expected order array
        PreparedStatement   ps = 
            prepareStatement("UPDATE CUSTOMER SET TWO_CHECK_CHAR = ?"); 
        ps.setString(1, NAMES[expected_order[3]]);
        ps.executeUpdate();

        // Set VARCHAR field to be third item im expected order array
        ps = 
            prepareStatement("UPDATE CUSTOMER SET TWO_CHECK_VARCHAR = ?"); 

        ps.setString(1, NAMES[expected_order[3]]);
        ps.executeUpdate();

        // check persistent compared to persistent - VARCHAR TO CHAR, 
        // should return rows bigger than 3rd in expected order.
        checkLangBasedQuery(
            "SELECT ID, NAME FROM CUSTOMER WHERE NAME > TWO_CHECK_CHAR ORDER BY NAME",
            full_row_set(
                expected_order,
                4, 
                expected_order.length - 1,
                true),
            true);

        // check persistent compared to persistent - CHAR TO VARCHAR, 
        // should return rows bigger than 3rd in expected order.
        checkLangBasedQuery(
            "SELECT ID, NAME FROM CUSTOMER WHERE TWO_CHECK_CHAR < NAME ORDER BY NAME",
            full_row_set(
                expected_order,
                4, 
                expected_order.length - 1,
                true),
            true);

        // check persistent compared to persistent - VARCHAR TO VARCHAR, 
        // should return rows bigger than 3rd in expected order.
        checkLangBasedQuery(
            "SELECT ID, NAME FROM CUSTOMER WHERE NAME > TWO_CHECK_VARCHAR ORDER BY NAME",
            full_row_set(
                expected_order,
                4, 
                expected_order.length - 1,
                true),
            true);

        // check persistent compared to persistent - CHAR TO CHAR, 
        // should return rows bigger than 3rd in expected order.
        checkLangBasedQuery( 
            "SELECT ID, NAME FROM CUSTOMER WHERE D3 > TWO_CHECK_CHAR ORDER BY NAME",
            full_row_set(
                expected_order,
                4, 
                expected_order.length - 1,
                true),
            true);

        // put back data the way it was on entry to test.
        rollback();
    }



    private void dropTable() throws SQLException 
    {
        dropTable("CUSTOMER");
        dropTable("EMPTY_TABLE");
    }

    private void runQueries(
    int         db_index,
    String      create_idx_qry,
    String      idx_name)
        throws SQLException 
    {
        Statement s = createStatement();

        if (create_idx_qry != null)
        {
            s.execute(create_idx_qry);
            commit();
        }

        // Simple check of getting all rows back in order
        checkLangBasedQuery(
            "SELECT ID, NAME FROM CUSTOMER ORDER BY NAME",
            full_row_set(
                EXPECTED_NAME_ORDER[db_index], 
                0, 
                EXPECTED_NAME_ORDER[db_index].length - 1, 
                true),
            true);

        // Simple check of getting all rows back in order
        checkLangBasedQuery(
            "SELECT ID, NAME FROM CUSTOMER ORDER BY NAME, ID",
            full_row_set(
                EXPECTED_NAME_ORDER[db_index], 
                0, 
                EXPECTED_NAME_ORDER[db_index].length - 1, 
                true),
            true);

        // Simple check of getting all rows back in opposite order
        checkLangBasedQuery(
            "SELECT ID, NAME FROM CUSTOMER ORDER BY NAME DESC",
            full_row_set(
                EXPECTED_NAME_ORDER[db_index], 
                0, 
                EXPECTED_NAME_ORDER[db_index].length - 1, 
                false),
            true);

        // Check <, <=, =, >=, > operators on constant vs. column
        checkSimpleCompare(EXPECTED_NAME_ORDER[db_index]);

        // Check compare of 2 persistent values, using join
        checkTwoPersistentCompare(EXPECTED_NAME_ORDER[db_index]);

        if (create_idx_qry != null)
            s.execute("DROP INDEX " + idx_name);

        commit();
    }

    /**
     * Test various like expressions against all string datatypes.
     *
     * T6: (DONE) test like
     * @throws SQLException
     **/
    private void runLikeTests(
    int         db_index)
        throws SQLException
    {
        setUpLikeTable();

        for (int i = 0; i < LIKE_TEST_CASES.length; i++)
        {
            if (verbose_debug)
            {
                System.out.println(
                    "Running like test[" + i + "] = " + LIKE_TEST_CASES[i]);
            }

            // varchar column - constant pattern
            checkLangBasedQuery(
                "SELECT ID, NAME_VARCHAR FROM CUSTOMER " + 
                    "WHERE NAME_VARCHAR LIKE '" + LIKE_TEST_CASES[i] + "'",
                full_row_single_value(
                    EXPECTED_LIKE_RESULTS[db_index][i],
                    LIKE_NAMES),
                true);

            // varchar column - parameter pattern
            checkParamQuery(
                "SELECT ID, NAME_VARCHAR FROM CUSTOMER " + 
                    "WHERE NAME_VARCHAR LIKE ?",
                new String[] {LIKE_TEST_CASES[i]},
                1,
                full_row_single_value(
                    EXPECTED_LIKE_RESULTS[db_index][i],
                    LIKE_NAMES),
                true);

            // long varchar column - constant
            checkLangBasedQuery(
                "SELECT ID, NAME_LONGVARCHAR FROM CUSTOMER " + 
                    "WHERE NAME_LONGVARCHAR LIKE '" + LIKE_TEST_CASES[i] + "'",
                full_row_single_value(
                    EXPECTED_LIKE_RESULTS[db_index][i],
                    LIKE_NAMES),
                true);

            // long varchar column - parameter
            checkParamQuery(
                "SELECT ID, NAME_LONGVARCHAR FROM CUSTOMER " + 
                    "WHERE NAME_LONGVARCHAR LIKE ?",
                new String[] {LIKE_TEST_CASES[i]},
                1,
                full_row_single_value(
                    EXPECTED_LIKE_RESULTS[db_index][i],
                    LIKE_NAMES),
                true);

            // clob column - constant
            checkLangBasedQuery(
                "SELECT ID, NAME_CLOB FROM CUSTOMER WHERE NAME_CLOB LIKE " +
                    "'" + LIKE_TEST_CASES[i] + "'",
                full_row_single_value(
                    EXPECTED_LIKE_RESULTS[db_index][i],
                    LIKE_NAMES),
                true);

            // clob column - parameter
            checkParamQuery(
                "SELECT ID, NAME_CLOB FROM CUSTOMER WHERE NAME_CLOB LIKE ?",
                new String[] {LIKE_TEST_CASES[i]},
                1,
                full_row_single_value(
                    EXPECTED_LIKE_RESULTS[db_index][i],
                    LIKE_NAMES),
                true);

            // char column, char includes blank padding so alter all these
            // tests cases to match for blanks at end also.
            checkLangBasedQuery(
                "SELECT ID, NAME_CHAR FROM CUSTOMER WHERE NAME_CHAR LIKE " + 
                    "'" + LIKE_CHAR_TEST_CASES[i] + "%'",
                full_row_single_value(
                    EXPECTED_LIKE_RESULTS[db_index][i],
                    LIKE_NAMES),
                true);

            // char column, char includes blank padding so alter all these
            // tests cases to match for blanks at end also.
            checkParamQuery(
                "SELECT ID, NAME_CHAR FROM CUSTOMER WHERE NAME_CHAR LIKE ?",
                new String[] {LIKE_CHAR_TEST_CASES[i] + "%"},
                1,
                full_row_single_value(
                    EXPECTED_LIKE_RESULTS[db_index][i],
                    LIKE_NAMES),
                true);
        }

        // test error thrown from LIKE on mismatched collation 
        String zero_row_syscat_query1 = 
            "SELECT * from SYS.SYSCOLUMNS where COLUMNNAME like 'nonmatchiing'";
        String zero_row_syscat_query2 = 
            "SELECT * from SYS.SYSCOLUMNS where 'nonmatchiing' like COLUMNNAME";
        String zero_row_syscat_query_param1 = 
            "SELECT * from SYS.SYSCOLUMNS where COLUMNNAME like ?";
        String zero_row_syscat_query_param2 = 
            "SELECT * from SYS.SYSCOLUMNS where ? like COLUMNNAME";
        String zero_row_syscat_query_param3 = 
            "SELECT count(*) from SYS.SYSCOLUMNS where ? like ?";

        if (!isDatabaseBasicCollation())
        {
            // collation of 'fred' picked up from current schema which is
            // territory based collation, but system column will have basic
            // collation.

            assertCompileError("42ZA2", zero_row_syscat_query1);
            assertCompileError("42ZA2", zero_row_syscat_query2);
            //The following 2 queries will work because ? in the query will
            //take it's collation from the context, which in this case would
            //mean from COLUMNNAME column in SYS.SYSCOLUMNS
            //
            checkParamQuery(
                    zero_row_syscat_query_param1, 
                    new String[] {"nonmatchiing"}, 1, null, true);
            checkParamQuery(
                    zero_row_syscat_query_param2, 
                    new String[] {"nonmatchiing"}, 1, null, true);
            checkParamQuery(
                    zero_row_syscat_query_param3, 
                    new String[] {"nonmatching", "matching"}, 2, 
                    new String[][] {{"0"}}, true);
        }
        else
        {
            checkLangBasedQuery(zero_row_syscat_query1, null, true);
            checkLangBasedQuery(zero_row_syscat_query2, null, true);
            checkParamQuery(
                    zero_row_syscat_query_param1, 
                    new String[] {"nonmatchiing"}, 1, null, true);
            checkParamQuery(
                    zero_row_syscat_query_param2, 
                    new String[] {"nonmatchiing"}, 1, null, true);
            checkParamQuery(
                    zero_row_syscat_query_param3, 
                    new String[] {"nonmatching", "123"}, 2, 
                    new String[][] {{"0"}}, true);
        }

        dropTable();
    }


    /**
     * test paths through alter table compress
     *
     * Tests:
     * T10: alter table compress with indexes
     **/
    private void runAlterTableCompress(
    int         db_index)
        throws SQLException 
    {
        Statement s = createStatement();

        setUpTable();

        s.execute("CREATE INDEX IDX1 ON CUSTOMER (NAME)");
        s.execute("CREATE INDEX IDX2 ON CUSTOMER (NAME, ID)");
        s.execute("CREATE INDEX IDX3 ON CUSTOMER (ID,   NAME)");
        s.execute("CREATE INDEX IDX4 ON CUSTOMER (ID)");
        s.execute("CREATE INDEX IDX5 ON CUSTOMER (ID, NAME, D1, D2, D3)");

        commit();

        // execute alter table compress which will build all new indexes and
        // base conglomerates, verify collation info correctly gets into new
        // entities.
        CallableStatement call_stmt = prepareCall(
            " call SYSCS_UTIL.SYSCS_COMPRESS_TABLE('APP', 'CUSTOMER', 1)");
        assertUpdateCount(call_stmt, 0);
 
        commit();

        runQueries(db_index, null, null);

        s.execute("DROP INDEX IDX1 ");
        s.execute("DROP INDEX IDX2 ");
        s.execute("DROP INDEX IDX3 ");
        s.execute("DROP INDEX IDX4 ");
        s.execute("DROP INDEX IDX5 ");

        // let's test abort get's back to right collation also.
        rollback();

        runQueries(db_index, null, null);

        dropTable();
        commit();
    }

    /**
     * Drop column test.
     * <p>
     * Drop column will drop and recreate base table and associated indexes,
     * need to test to make sure correct colation ids get passed to new
     * containers. 
     *
     * Tests:
     * T11: alter table drop column with indexes
     **/
    private void runAlterTableDropColumn(
    Connection  conn,
    int         db_index)
        throws SQLException 
    {
        Statement s = conn.createStatement();

        setUpTable();

        s.execute("ALTER TABLE CUSTOMER DROP COLUMN D1");
        runQueries(db_index, null, null);

        s.execute("CREATE INDEX IDX1 ON CUSTOMER (NAME)");
        s.execute("ALTER TABLE CUSTOMER DROP COLUMN D2");
        runQueries(db_index, null, null);
        conn.rollback();

        dropTable();
        commit();
    }

    /**
     * Add column test.
     * <p>
     * Add column adds a new template column which requires a collation
     * info related store update.  Test that added column had right 
     * collation setting.
     *
     * Tests:
     * T12: alter table add column with index
     **/
    private void runAlterTableAddColumn(
    int         db_index)
        throws SQLException 
    {
        Statement s = createStatement();

        setUpTable();

        s.execute("ALTER TABLE CUSTOMER DROP COLUMN NAME");
        s.execute("ALTER TABLE CUSTOMER ADD COLUMN NAME CHAR(40)");
        s.execute("UPDATE CUSTOMER SET NAME = D1");
        runQueries(db_index, null, null);

        s.execute("CREATE INDEX IDX1 ON CUSTOMER (NAME)");
        runQueries(db_index, null, null);

        dropTable();

        commit();
    }

    /**
     * Bulk insert test.
     * <p>
     * Tests code path through create conglomerate code executed as part of
     * a bulk table insert.  In empty table and replace case the bulk table
     * code will create new conglomerates for the base table and index table
     * and this tests the code that the correct collation is associated with
     * the new tables/indexes.
     *
     * Tests:
     * T13: (DONE) bulk insert into empty table, with and without indexes
     * T14: (DONE) bulk insert replace, with and without indexes
     **/
    private void runBulkInsert(
    int         db_index)
        throws SQLException 
    {
        Statement s = createStatement();

        setUpTable();

        //DERBY-4435
        String emptyFileName =
            (SupportFilesSetup.getReadWrite("empty_file.dat")).getPath();
        s.execute("DELETE FROM EMPTY_TABLE");
        //there is no data in EMPTY_TABLE so empty_file.dat will be empty 
        //after export
        doExportTable("APP", "EMPTY_TABLE", emptyFileName, null, null, "UTF-16");
        commit();
        // bulk insert with replace to empty table/one index from an empty file 
        // import empty_file.dat into EMPTY_TABLE 
        doImportTable(
                "APP", "EMPTY_TABLE", emptyFileName, "|", "`", null, 1);

        commit();

        // export CUSTOMER date to names.dat
        String fileName =
            (SupportFilesSetup.getReadWrite("names.dat")).getPath();

        doExportTable("APP", "CUSTOMER", fileName, null, null, "UTF-16");

        commit();

        // bulk insert to empty table, no indexes without replace 
        // (last arg 0 = no replace).
        s.execute("DELETE FROM CUSTOMER");
        commit();

        // checkGetColumn(conn);

        doImportTable(
            "APP", "CUSTOMER", fileName, null, null, "UTF-16", 0);
        runQueries(db_index, null, null);

        // bulk insert to empty table, with indexes without replace 
        // (last arg 0 = no replace).
        s.execute("DELETE FROM CUSTOMER");
        s.execute("CREATE INDEX IDX1 ON CUSTOMER (NAME)");
        s.execute("CREATE INDEX IDX2 ON CUSTOMER (NAME, ID)");
        s.execute("CREATE INDEX IDX3 ON CUSTOMER (ID,   NAME)");
        s.execute("CREATE INDEX IDX4 ON CUSTOMER (ID)");
        s.execute("CREATE INDEX IDX5 ON CUSTOMER (ID, NAME, D1, D2, D3)");
        doImportTable(
            "APP", "CUSTOMER", fileName, null, null, "UTF-16", 0);
        runQueries(db_index, null, null);
        s.execute("DROP INDEX IDX1 ");
        s.execute("DROP INDEX IDX2 ");
        s.execute("DROP INDEX IDX3 ");
        s.execute("DROP INDEX IDX4 ");
        s.execute("DROP INDEX IDX5 ");

        // bulk insert to non-empty table, no indexes with replace, call 
        // import first to double the rows in the table.
        // (last arg to Import 1 = replace).
        doImportTable(
            "APP", "CUSTOMER", fileName, null, null, "UTF-16", 0);
        doImportTable(
            "APP", "CUSTOMER", fileName, null, null, "UTF-16", 1);
        runQueries(db_index, null, null);

        // bulk insert to non-empty table, indexes with replace, call 
        // import first to double the rows in the table.
        // (last arg to Import 1 = replace).
        s.execute("CREATE INDEX IDX1 ON CUSTOMER (NAME)");
        s.execute("CREATE INDEX IDX2 ON CUSTOMER (NAME, ID)");
        s.execute("CREATE INDEX IDX3 ON CUSTOMER (ID,   NAME)");
        s.execute("CREATE INDEX IDX4 ON CUSTOMER (ID)");
        s.execute("CREATE INDEX IDX5 ON CUSTOMER (ID, NAME, D1, D2, D3)");
        doImportTable(
            "APP", "CUSTOMER", fileName, null, null, "UTF-16", 0);
        doImportTable(
            "APP", "CUSTOMER", fileName, null, null, "UTF-16", 1);
        runQueries(db_index, null, null);
        s.execute("DROP INDEX IDX1 ");
        s.execute("DROP INDEX IDX2 ");
        s.execute("DROP INDEX IDX3 ");
        s.execute("DROP INDEX IDX4 ");
        s.execute("DROP INDEX IDX5 ");

        dropTable();

        commit();
    }


    private static final String[] derby2670_pattern =
    {
        "%",
        "a%",
        "b%",
        "c%",
        "%a%",
        "%b%",
        "%c%",
        "%a",
        "%b",
        "%c"
    };

    private static final String[][][] derby2670_pattern_result = 
    {
        // pattern = % 
        { {"a"}, 
          {"A"}, 
          {" a"}, 
          {"-a"}, 
          {"\u00ADa"}, 
          {"b"}, 
          {"B"}, 
          {" b"}, 
          {"-b"}, 
          {"\u00ADb"}, 
          {"C"},
          {"ekstra\u00ADarbeid"}, 
          {"ekstrabetaling"}, 
          {"ekstraarbeid"}, 
          {"Wanvik"}, 
          {"W\u00E5gan"},
          {"Waagan"}, 
          {"W\u00E5han"}
        },
        // pattern = a% 
        { {"a"} },
        // pattern = b% 
        { {"b"} },
        // pattern = c% 
        null,
        // pattern = %a% 
        { {"a"}, 
          {" a"}, 
          {"-a"}, 
          {"\u00ADa"}, 
          {"ekstra\u00ADarbeid"}, 
          {"ekstrabetaling"}, 
          {"ekstraarbeid"}, 
          {"Wanvik"}, 
          {"W\u00E5gan"},
          {"Waagan"}, 
          {"W\u00E5han"}
        },
        // pattern = %b% 
        { {"b"}, 
          {" b"}, 
          {"-b"}, 
          {"\u00ADb"}, 
          {"ekstra\u00ADarbeid"}, 
          {"ekstrabetaling"}, 
          {"ekstraarbeid"}
        }, 
        // pattern = %c% 
        null,
        // pattern = %a
        { {"a"}, 
          {" a"}, 
          {"-a"}, 
          {"\u00ADa"} 
        }, 
        // pattern = %b
        { {"b"}, 
          {" b"}, 
          {"-b"}, 
          {"\u00ADb"}
        }, 
        // pattern = %c
        null
    };

    /**
     * Test case for DERBY-2670 - problem with like in no like processing.
     * <p>
     * Before fix, the table/query below would return results like B and
     * C, obviously wrong for like %a%.  The code was incorrectly caching
     * collation key info in a DataValueDescriptor across the reuse of the
     * holder object from one row to the next.
     * <p>
     * Added more patterns to also test DERBY-2710 and DERBY-2706, both
     * to do with bad like optimization which can not be applied to collation
     * based like.
     **/
    private void runDerby2670()
        throws SQLException
    {
        Statement s = createStatement();

        String[] rows = 
            { "Waagan", "W\u00E5han", "Wanvik", "W\u00E5gan", "ekstrabetaling",
              "ekstraarbeid", "ekstra\u00ADarbeid", "\u00ADa", "a", "\u00ADb", 
              "b", "-a", "-b", " a", " b", "A", "B", "C" 
            };


        s.executeUpdate("create table t (x varchar(20))");
        PreparedStatement ps = prepareStatement("insert into t values ?");
        for (int i = 0; i < rows.length; i++) {
            ps.setString(1, rows[i]);
            ps.executeUpdate();
        }
        ps.close();

        Assert.assertEquals(
            "source and result arrays do not match for derby2670",
            derby2670_pattern_result.length, derby2670_pattern.length);

        String like_qry = "select * from t where x like ";
        PreparedStatement ps_like = 
            prepareStatement("select * from t where x like ?");
        PreparedStatement ps_like_orderby = 
            prepareStatement("select * from t where x like ? order by x");

        for (int i = 0; i < derby2670_pattern.length; i++)
        {
            // Try just unordered like with constant pattern
            String qry = like_qry + "'" + derby2670_pattern[i] + "'";

            checkLangBasedQuery(
                qry, derby2670_pattern_result[i], false);

            // add an order by 
            qry += " order by x";

            checkLangBasedQuery(
                qry, derby2670_pattern_result[i], false);

            // try parameter for pattern
            ps_like.setString(1, derby2670_pattern[i]);
            ResultSet rs = ps_like.executeQuery();

            if (derby2670_pattern_result[i] == null)
                JDBC.assertEmpty(rs);
            else
                JDBC.assertUnorderedResultSet(rs, derby2670_pattern_result[i]);

            rs.close();
            rs = null;

            // try parameter for pattern
            ps_like_orderby.setString(1, derby2670_pattern[i]);
            rs = ps_like_orderby.executeQuery();

            if (derby2670_pattern_result[i] == null)
                JDBC.assertEmpty(rs);
            else
                JDBC.assertFullResultSet(rs, derby2670_pattern_result[i]);

            rs.close();
            rs = null;
        }

        // add an index and try it again.
        s.executeUpdate("create index t_idx on t (x)");
        like_qry = "select * from t where x like ";

        for (int i = 0; i < derby2670_pattern.length; i++)
        {
            // Try just unordered like with constant pattern
            String qry = like_qry + "'" + derby2670_pattern[i] + "'";

            checkLangBasedQuery(
                qry, derby2670_pattern_result[i], false);

            // add an order by 
            qry += " order by x";

            checkLangBasedQuery(
                qry, derby2670_pattern_result[i], false);

            // try parameter for pattern
            ps_like.setString(1, derby2670_pattern[i]);
            ResultSet rs = ps_like.executeQuery();

            if (derby2670_pattern_result[i] == null)
                JDBC.assertEmpty(rs);
            else
                JDBC.assertUnorderedResultSet(rs, derby2670_pattern_result[i]);

            rs.close();
            rs = null;

            // try parameter for pattern
            ps_like_orderby.setString(1, derby2670_pattern[i]);
            rs = ps_like_orderby.executeQuery();


            if (derby2670_pattern_result[i] == null)
                JDBC.assertEmpty(rs);
            else
                JDBC.assertFullResultSet(rs, derby2670_pattern_result[i]);

            rs.close();
            rs = null;
        }



        s.executeUpdate("drop table t");

        commit();
        
        // cleanup
        ps_like_orderby.close();
        ps_like.close();
        s.close();
    }

    /**
     * Tests that truncating a table with indexes leaves us with a valid set
     * of conglomerates.
     */
    private void runDerby5530TruncateIndex()
            throws SQLException {
        setAutoCommit(false);
        Statement s = createStatement();
        s.executeUpdate("create table d5530i (val varchar(10))");
        s.executeUpdate("create index idx on d5530i(val)");
        s.executeUpdate("truncate table d5530i");
        s.executeUpdate("insert into d5530i values 'one', 'two'");
        ResultSet rs = s.executeQuery("select * from d5530i");
        JDBC.assertUnorderedResultSet(rs, new String[][] {{"one"}, {"two"}});
        rollback();
    }

    /**
     * Tests that truncating a table without indexes leaves us with a valid
     * conglomerate.
     */
    private void runDerby5530TruncateNoIndex()
            throws SQLException {
        setAutoCommit(false);
        Statement s = createStatement();
        s.executeUpdate("create table d5530 (val varchar(10))");
        s.executeUpdate("truncate table d5530");
        s.executeUpdate("insert into d5530 values 'one', 'two'");
        ResultSet rs = s.executeQuery("select * from d5530");
        JDBC.assertUnorderedResultSet(rs, new String[][] {{"one"}, {"two"}});
        rollback();
    }

    /**
     * Shared code to run all test cases against a single collation.
     * <p>
     * Pass in the index of which TEST_DATABASE database to test.  So
     * for instance to run the default, pass in 0.
     * <p>
     *
     * @param db_index  index of which test to run.
     *
     * @exception  SQLException
     **/
    private void runTestIter(
    int         db_index) 
        throws SQLException 
    {
        setUpTable();

        // run tests against base table no index, exercise heap path
        // Tests the following:
        // T0: Heap based compare using predicate pushing
        // T3: order by on heap using in memory sorter
        runQueries(db_index, null, null);

        // run tests against base table with non unique index
        // Tests the following:
        // T1: (DONE) Index based compare start/stop predicates on index
        runQueries(
            db_index, 
            "CREATE INDEX NAME_IDX ON CUSTOMER (NAME)", "NAME_IDX");

        // run tests against base table with only unique index
        runQueries(
            db_index, 
            "CREATE UNIQUE INDEX IDX ON CUSTOMER (NAME)", "IDX");

        // run tests against base table with non unique descending index
        runQueries(
            db_index, 
            "CREATE INDEX NAME_IDX ON CUSTOMER (NAME DESC)", "NAME_IDX");

        // run tests against base table with unique descending index
        runQueries(
            db_index, 
            "CREATE UNIQUE INDEX IDX ON CUSTOMER (NAME DESC)", "IDX");

        // run tests against base table with unique composite key
        runQueries(
            db_index, 
            "CREATE UNIQUE INDEX IDX ON CUSTOMER (NAME, ID)", "IDX");

        dropTable();

        // the following tests mess with column values and ddl, so they
        // are going to drop and recreate the small test data table.
        runDERBY_2703(db_index);

        runAlterTableAddColumn(db_index);

        runAlterTableCompress(db_index);

        // because of jvm issue described in DERBY-3055, do not
        // test this with J2ME/JSR169.
        if (JDBC.vmSupportsJDBC3())
            runBulkInsert(db_index);

        runLikeTests(db_index);

        runDerby5530TruncateNoIndex();
        runDerby5530TruncateIndex();

        /*
        TODO -MIKEM, this test does not work yet.
        runAlterTableDropColumn(conn, db_index);
        */

        commit();
    }

    /**************************************************************************
     * Public Methods of This class:
     **************************************************************************
     */

    public void testDefaultCollation() throws SQLException
    {
        assertTrue(isDatabaseBasicCollation());
        runTestIter(TEST_DEFAULT);
    }
    public void testEnglishCollation() throws SQLException
    {
        assertFalse(isDatabaseBasicCollation());
        runTestIter(TEST_ENGLISH);
    }

    public void testPolishCollation() throws SQLException
    {
        assertFalse(isDatabaseBasicCollation());
        runTestIter(TEST_POLISH);
    }
    public void testNorwayCollation() throws SQLException
    {
        assertFalse(isDatabaseBasicCollation());
        runDerby2670();
        runTestIter(TEST_NORWAY);
    }
    /**
     * Test creating a TERRITORY_BASED collated database by only setting
     * the collation attribute.  The Territory will be picked up from the
     * default territory of the JVM.
     *
     * Tests:
     * T24: DERBY-2669 If no territory attribute is specified at create 
     *      database time, then create collated db based on default 
     *      territory of Database.
     **/
    public void testDefaultJVMTerritoryCollation() throws SQLException
    {
        Locale locale = Locale.getDefault();

        if (locale.getLanguage().equals("en"))
        {
            testEnglishCollation();
        }
        else if (locale.getLanguage().equals("no"))
        {
            testNorwayCollation();
        }
        else if (locale.getLanguage().equals("po"))
        {
            testPolishCollation();
        }
    }
    
    public static Test suite() 
    {
        // only test in embedded mode, all tests are server side actions.
        
        TestSuite suite = new TestSuite("CollationTest2");
        suite.addTest(new CollationTest2("testDefaultCollation"));
        suite.addTest(collatedTest("en", "testEnglishCollation"));
        suite.addTest(caseInsensitiveCollationSuite());
        
        // Only add tests for other locales if they are in fact supported 
        // by the jvm.
        Locale[] availableLocales = Collator.getAvailableLocales();
        boolean norwegian = false; 
        boolean polish = false;
        for (int i=0; i<availableLocales.length ; i++) {
            if("no".equals(availableLocales[i].getLanguage())) {
                norwegian = true;
            }
            if("pl".equals(availableLocales[i].getLanguage())) {
                polish = true;
            }
        }
        if(norwegian) {
            suite.addTest(collatedTest("no_NO", "testNorwayCollation"));
        }
        if(polish) {
            suite.addTest(collatedTest("pl", "testPolishCollation"));
        }
        suite.addTest(collatedTest(null, "testDefaultJVMTerritoryCollation"));
        
        // add support to use external files for import/export calls.
        Test test = new SupportFilesSetup(suite);

        // turn on log statement text for sequence of statements in derby.log.  
        if (verbose_debug)
        {
            Properties props = new Properties();
            props.setProperty("derby.language.logStatementText", "true");
            test = new SystemPropertyTestSetup(test, props);
        }

        return test;
    }
    
    private static Test collatedTest(String locale, String fixture)
    {
        return Decorator.territoryCollatedDatabase(
                new CollationTest2(fixture), locale);
    }

    /**
     * Returns a suite of tests running with a collation strength resulting
     * in case insensitivity.
     *
     * @return A suite of tests.
     */
    private static Test caseInsensitiveCollationSuite() {
        TestSuite suite = new TestSuite("Case insensitive specific tests");
        suite.addTest(new CollationTest2("ci_testDerby5367")); 
        return Decorator.territoryCollatedCaseInsensitiveDatabase(
                suite, "en_US");
    }
}
