/*
 *
 * Derby - Class BlobClob4BlobTest
 *
 * 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.jdbcapi;

import java.io.ByteArrayInputStream;
import java.io.CharArrayReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.io.StringReader;
import java.io.UnsupportedEncodingException;
import java.sql.Blob;
import java.sql.Clob;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Types;
import java.util.Arrays;
import java.util.Random;
import java.util.zip.CRC32;
import junit.framework.Test;
import org.apache.derbyTesting.functionTests.util.Formatters;
import org.apache.derbyTesting.functionTests.util.streams.ByteAlphabet;
import org.apache.derbyTesting.functionTests.util.streams.CharAlphabet;
import org.apache.derbyTesting.functionTests.util.streams.LoopingAlphabetReader;
import org.apache.derbyTesting.functionTests.util.streams.LoopingAlphabetStream;
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.Decorator;
import org.apache.derbyTesting.junit.JDBC;
import org.apache.derbyTesting.junit.TestConfiguration;
import org.apache.derbyTesting.junit.Utilities;

/**
 * Test of JDBC blob and clob
 */
public class BlobClob4BlobTest extends BaseJDBCTestCase {

    /** Creates a new instance of BlobClob4BlobTest */
    public BlobClob4BlobTest(String name) {
        super(name);
    }

    /**
     * Set up the conection to the database.
     */
    public void setUp() throws  Exception {
        getConnection().setAutoCommit(false);

        // creating small tables then add large column - that way forcing table
        // to have default small page size, but have large rows.

        Statement stmt = createStatement();
        stmt.executeUpdate("CREATE TABLE testClob (b INT, c INT)");
        stmt.executeUpdate("ALTER TABLE testClob ADD COLUMN a CLOB(300K)");

        // multiple tests depend on small page size, make sure size is 4k
        checkSmallPageSize(stmt, "TESTCLOB");

        stmt.executeUpdate("CREATE TABLE testBlob (b INT)");
        stmt.executeUpdate("ALTER TABLE testBlob ADD COLUMN a blob(300k)");
        stmt.executeUpdate("ALTER TABLE testBlob ADD COLUMN crc32 BIGINT");

        // multiple tests depend on small page size, make sure size is 4k
        checkSmallPageSize(stmt, "TESTBLOB");

        stmt.close();
        commit();
    }

    protected void tearDown() throws Exception {
        rollback();
        Statement stmt = createStatement();
        stmt.executeUpdate("DROP TABLE testClob");
        stmt.executeUpdate("DROP TABLE testBlob");
        stmt.close();
        commit();
        super.tearDown();
    }

    /***                TESTS               ***/

    /**
     * DERBY-3085.  Test update where streamed parameter is not 
     * consumed by the server. Network Server needs to clean-up 
     * after execution.
     * 
     */
    public void testUnconsumedParameter() throws SQLException
    {
        Connection conn = getConnection();
        conn.setAutoCommit(false);
        Statement s = conn.createStatement();
        // Test table with no rows.
        s.executeUpdate("create table testing(num int, addr varchar(40), contents blob(16M))");
        // no rows inserted so there is no match.
        byte[] data = new byte[ 38000];
        for (int i = 0; i < data.length; i++)
            data[i] = 'a';
        ByteArrayInputStream is = new ByteArrayInputStream( data);           
        String sql = "UPDATE testing SET Contents=? WHERE num=1";
        
        PreparedStatement ps = prepareStatement( sql);
        ps.setBinaryStream( 1, is,data.length);
        //DERBY-4312 Make sure commit() doesn't interfere here
        commit();
        ps.executeUpdate();          
        // Make sure things still work ok when we have a parameter that does get consumed.
        // insert a matching row.
        s.executeUpdate("insert into testing values (1,null,null)");
        is = new ByteArrayInputStream(data);
        ps.setBinaryStream( 1, is,data.length);
        ps.executeUpdate();
        // Check update occurred
        ResultSet rs = s.executeQuery("select length(contents) from testing where num = 1");
        JDBC.assertSingleValueResultSet(rs, "38000");
        ps.close();
        conn.commit();
        // Check the case where there are rows inserted but there is no match.
        is = new ByteArrayInputStream( data);           
        sql = "UPDATE testing SET Contents=? WHERE num=2";
        ps = prepareStatement( sql);
        ps.setBinaryStream( 1, is,data.length);
        ps.executeUpdate();
        ps.close();
        s.executeUpdate("drop table testing");
        conn.commit();
        
        // Test with multiple parameters
        s.executeUpdate("create table testing(num int, addr varchar(40), contents blob(16M),contents2 blob(16M))");
        
        is = new ByteArrayInputStream( data);
        ByteArrayInputStream is2 = new ByteArrayInputStream(data);
        sql = "UPDATE testing SET Contents=?, contents2=?  WHERE num=1";

        ps = prepareStatement( sql);
        ps.setBinaryStream( 1, is,data.length);
        ps.setBinaryStream(2, is2,data.length);
        ps.executeUpdate();
        
        
        // multiple parameters and matching row
        s.executeUpdate("insert into testing values (1,'addr',NULL,NULL)");
        is = new ByteArrayInputStream( data);
        is2 = new ByteArrayInputStream(data);
        ps.setBinaryStream( 1, is,data.length);
        ps.setBinaryStream(2, is2,data.length);
        ps.executeUpdate();
        rs = s.executeQuery("select length(contents), length(contents2) from testing where num = 1");
        JDBC.assertFullResultSet(rs, new String[][] {{"38000","38000"}});
        rs.close();
        s.executeUpdate("drop table testing");
        
        // With Clob
        s.executeUpdate("create table testing(num int, addr varchar(40), contents Clob(16M))");
        char[] charData = new char[ 38000];
        for (int i = 0; i < data.length; i++)
       data[i] = 'a';
        CharArrayReader reader = new CharArrayReader( charData);            
        sql = "UPDATE testing SET Contents=? WHERE num=1";

       ps = prepareStatement( sql);
       ps.setCharacterStream( 1, reader,charData.length);
       ps.executeUpdate();
       // with a matching row
       s.executeUpdate("insert into testing values (1,null,null)");
       reader = new CharArrayReader(charData);
       ps.setCharacterStream( 1, reader,data.length);
       ps.executeUpdate();
       // Check update occurred
       rs = s.executeQuery("select length(contents) from testing where num = 1");
       JDBC.assertSingleValueResultSet(rs, "38000");
       s.executeUpdate("drop table testing");
       ps.close();
       
       conn.commit();
        
    }

    /**
     * Test that it is possible to change the isolation level after reading a
     * BLOB (DERBY-3427).
     */
    public void testIsolationLevelChangeAfterRead() throws SQLException {
        ResultSet rs =
            createStatement().executeQuery("VALUES CAST(X'FFFF' AS BLOB)");
        JDBC.assertDrainResults(rs);
        getConnection().setTransactionIsolation(
            Connection.TRANSACTION_SERIALIZABLE);
    }

    /**
     * Tests PreparedStatement.setCharacterStream
     */
    public void testSetCharacterStream() throws Exception {
        int clobLength = 5009;

        // insert a streaming column
        PreparedStatement ps = prepareStatement(
                "insert into testClob (a) values(?)");
        Reader streamReader = new LoopingAlphabetReader(
                clobLength, CharAlphabet.tamil());
        ps.setCharacterStream(1, streamReader, clobLength);
        //DERBY-4312 make sure commit() doesn't interfere
        commit();
        ps.executeUpdate();
        streamReader.close();
        ps.close();
        commit();

        Statement stmt = createStatement();
        ResultSet rs = stmt.executeQuery("SELECT a FROM testClob");
        while (rs.next()) {
            Clob clob = rs.getClob(1);
            assertEquals("FAIL - wrong clob length", clobLength, clob.length());
            Reader clobValue = clob.getCharacterStream();
            Reader origValue = new LoopingAlphabetReader(
                    clobLength, CharAlphabet.tamil());

            assertTrue("New clob value did not match",
                    compareReaders(origValue, clobValue));
            origValue.close();
            clobValue.close();
        }
        rs.close();
        stmt.close();

        commit();

    }

    /**
     *  basic test of getAsciiStream also tests length
     */
    public void testGetAsciiStream() throws Exception {
        byte[] buff = new byte[1024];

        insertDefaultData();

        Statement stmt = createStatement();
        ResultSet rs = stmt.executeQuery("SELECT a, b FROM testClob");

        // fetch row back, get the column as a clob.
        Clob clob;
        int clobLength;
        while (rs.next()) {
            // get the first column in select as a clob
            clob = rs.getClob(1);
            clobLength = rs.getInt(2);

            if (clob != null) {
                assertEquals("FAIL - wrong clob.length()",
                        clobLength, clob.length());

                InputStream fin = clob.getAsciiStream();
                int columnSize = 0;
                int size = -1;
                do {
                    size = fin.read(buff);
                    columnSize += (size > 0) ? size : 0;
                } while (size >= 0);

                assertEquals("FAIL - wrong column size",
                        clobLength, columnSize);
            } else {
                assertTrue("Clob was null but length was not 0",
                        (clobLength == 0));
            }
        }
        rs.close();
        stmt.close();

        commit();
    }

    /**
     * basic test of getCharacterStream also tests length
     */
    public void testGetCharacterStream() throws Exception {

        char[] buff = new char[128];

        insertDefaultData();

        Statement stmt = createStatement();
        ResultSet rs = stmt.executeQuery("select a , b from testClob");
        ResultSetMetaData met = rs.getMetaData();

        // fetch row back, get the column as a clob.
        int clobLength = 0;
        while (rs.next()) {
            // get the first column as a clob
            Clob clob = rs.getClob(1);
            clobLength = rs.getInt(2);
            if (clob != null) {
                assertEquals("FAIL - wrong clob.length()",
                        clobLength, clob.length());

                Reader reader = clob.getCharacterStream();
                int columnSize = 0;
                int size = -1;
                do {
                    size = reader.read(buff);
                    columnSize += (size >= 0) ? size : 0;
                } while (size >= 0);

                assertEquals("FAIL - wrong column size",
                        clobLength, columnSize);
            } else {
                assertTrue("Clob was null but length was not 0",
                        (clobLength == 0));
            }
        }
        rs.close();
        stmt.close();

        commit();
    }

    /**
     * test of getCharacterStream on a table containing unicode characters
     */
    public void testGetCharacterStreamWithUnicode() throws Exception {
        String[] unicodeStrings = {
            "\u0061\u0062\u0063",
            "\u0370\u0371\u0372",
            "\u05d0\u05d1\u05d2"};
        insertUnicodeData(unicodeStrings);

        Statement stmt = createStatement();
        ResultSet rs = stmt.executeQuery("SELECT a, b, c FROM testClob");

        int clobLength = 0, arrayIndex = 0;
        while (rs.next()) {
            clobLength = rs.getInt(2);
            arrayIndex = rs.getInt(3);
            Clob clob = rs.getClob(1);
            if (clob != null) {
                assertEquals("FAIL - wrong clob.length()",
                        clobLength, clob.length());

                Reader clobValue = clob.getCharacterStream();
                if (arrayIndex > 0) {
                    char[] buff = new char[3];
                    clobValue.read(buff);
                    assertEquals("Clob value does not match unicodeString",
                            unicodeStrings[arrayIndex],
                            new String(buff));
                    assertEquals("Expected end of stream",
                            -1, clobValue.read());
                } else {
                    Reader origValue = new LoopingAlphabetReader(
                            clobLength, CharAlphabet.tamil());
                    compareReaders(origValue, clobValue);
                }
            } else {
                assertTrue("Clob was null but length was not 0",
                        (clobLength == 0));
            }
        }
        rs.close();
        stmt.close();

        commit();
    }

    /**
     * Test triggers on CLOB columns.
     */
    public void baseTestTriggersWithClobColumn(boolean useOrderBy) 
        throws Exception {

        insertDefaultData();

        Statement stmt = createStatement();
        stmt.executeUpdate(
                "CREATE TABLE testClobTriggerA (a CLOB(400k), b int)");
        stmt.executeUpdate(
                "CREATE TABLE testClobTriggerB (a CLOB(400k), b int)");
        stmt.executeUpdate(
                "create trigger T13A after update on testClob " +
                "referencing new as n old as o " +
                "for each row "+
                "insert into testClobTriggerA(a, b) values (n.a, n.b)");
        stmt.executeUpdate(
                "create trigger T13B after INSERT on testClobTriggerA " +
                "referencing new table as n " +
                "for each statement "+
                "insert into testClobTriggerB(a, b) select n.a, n.b from n");

        commit();

        // Fire the triggers
        stmt.executeUpdate("UPDATE testClob SET b = b + 0");
        commit();

        // Verify the results
        Statement origSt = createStatement();
        Statement trigASt = createStatement();
        Statement trigBSt = createStatement();

        ResultSet origRS  = null;
        ResultSet trigARS = null;
        ResultSet trigBRS = null;

        if (useOrderBy) {
            origRS = 
                origSt.executeQuery(
                    "select a, length(a), b  from testClob order by b");
            trigARS = 
                trigASt.executeQuery(
                    "select a, length(a), b from testClobTriggerA order by b");
            trigBRS = 
                trigBSt.executeQuery(
                    "select a, length(a), b from testClobTriggerB order by b");

        } else {
            origRS = 
                origSt.executeQuery(
                        "select a, length(a), b  from testClob");
            trigARS = 
                trigASt.executeQuery(
                        "select a, length(a), b from testClobTriggerA");
            trigBRS = 
                trigBSt.executeQuery(
                        "select a, length(a), b from testClobTriggerB");
        }

        int count = 0;
        while (origRS.next()) {
            count ++;
            assertTrue("row trigger produced less rows " +
                    count, trigARS.next());
            assertTrue("statement trigger produced less rows " +
                    count, trigBRS.next());

            Clob origClob = origRS.getClob(1);
            if (origClob != null) {
                assertEquals("FAIL - Invalid checksum for row trigger",
                        getStreamCheckSum(origClob.getAsciiStream()),
                        getStreamCheckSum(trigARS.getClob(1).getAsciiStream()));
                assertEquals("FAIL - Invalid checksum for statement trigger",
                        getStreamCheckSum(origClob.getAsciiStream()),
                        getStreamCheckSum(trigBRS.getClob(1).getAsciiStream()));
            }

            assertEquals("FAIL - Invalid length in row trigger",
                    origRS.getInt(2), trigARS.getInt(2));
            assertEquals("FAIL - Invalid length in statement trigger",
                    origRS.getInt(2), trigBRS.getInt(2));

            assertEquals("FAIL - Length not updated on row trigger",
                    origRS.getInt(3), trigARS.getInt(3));
            assertEquals("FAIL - Length not updated on statement trigger",
                    origRS.getInt(3), trigBRS.getInt(3));
        }

        origRS.close();
        trigARS.close();
        trigBRS.close();
        origSt.close();
        trigASt.close();
        trigBSt.close();

        stmt.executeUpdate("DROP TRIGGER T13A");
        stmt.executeUpdate("DROP TRIGGER T13B");
        stmt.executeUpdate("DROP TABLE testClobTriggerB");
        stmt.executeUpdate("DROP TABLE testClobTriggerA");

        stmt.close();
        commit();
    }

    /**
     * Test triggers on CLOB columns.
     * <p>
     * Call with order by in the query, this causes a path through the
     * sorter.
     **/
    public void testTriggersWithClobColumnOrderBy() 
        throws Exception {

        baseTestTriggersWithClobColumn(true);
    }

    /**
     * Test triggers on CLOB columns.
     * <p>
     * Call with no order by in the query, thus a code path not through
     * the sorter.
     **/
    public void testTriggersWithClobColumn() 
        throws Exception {

        baseTestTriggersWithClobColumn(false);
    }


    /**
     * test Clob.getSubString() method
     */
    public void testGetSubString() throws Exception {
        insertDefaultData();

        Statement stmt = createStatement();
        ResultSet rs = stmt.executeQuery("select a, b from testClob");
        int clobLength = 0;
        Clob clob;
        while (rs.next()) {
            clob = rs.getClob(1);
            if (clob == null)
                continue;
            clobLength = rs.getInt(2);
            verifyInterval(clob, 9905, 50, 0, clobLength);
            verifyInterval(clob, 5910, 150, 1, clobLength);
            verifyInterval(clob, 5910, 50, 2, clobLength);
            verifyInterval(clob, 204, 50, 3, clobLength);
            verifyInterval(clob, 68, 50, 4, clobLength);
            verifyInterval(clob, 1, 50, 5, clobLength);
            verifyInterval(clob, 1, 1, 6, clobLength);
            verifyInterval(clob, 1, 0, 7, clobLength); // length 0 at start
            verifyInterval(clob, clobLength + 1, 0, 8, clobLength); // and end
            if (clobLength > 100) {
                String res = clob.getSubString(clobLength-99,200);
                assertEquals("FAIL - wrong length of substring",
                        100, res.length());
            }
        }
        rs.close();
        stmt.close();
    }

    /**
     * test getSubString with unicode
     */
    public void testGetSubStringWithUnicode() throws Exception {
        String[] unicodeStrings = {
            "\u0061\u0062\u0063",
            "\u0370\u0371\u0372",
            "\u05d0\u05d1\u05d2"};
        insertUnicodeData(unicodeStrings);

        Statement stmt = createStatement();
        ResultSet rs = stmt.executeQuery("select a, b, c from testClob");
        int clobLength = 0, arrayIndex = 0;
        Clob clob;
        while (rs.next()) {
            clob = rs.getClob(1);
            clobLength = rs.getInt(2);
            arrayIndex = rs.getInt(3);
            if (clob != null) {
                if (arrayIndex >= 0) {
                    assertEquals("FAIL - wrong substring returned",
                            unicodeStrings[arrayIndex],
                            clob.getSubString(1, 3));
                } else {
                    if (clob.length() > 0) {
                        long charsToRead = Math.min((clob.length() / 3), 2048);
                        char[] charValue = new char[(int)charsToRead];
                        Reader clobReader = clob.getCharacterStream();
                        clobReader.read(charValue);
                        clobReader.read(charValue);
                        String subString = clob.getSubString(charsToRead + 1,
                                (int)charsToRead);
                        assertEquals("FAIL - wrong substring length",
                                charValue.length, subString.length());
                        for (int i=0; i< charValue.length; i++) {
                            assertEquals("FAIL - wrong substring returned at " +
                                    i, charValue[i], subString.charAt(i));
                        }
                    }
                }
            }
        }
        rs.close();
        stmt.close();
    }

    /**
     * test position with a String argument
     */
    public void testPositionString() throws Exception {
        insertDefaultData();

        runPositionStringTest();
    }

    /**
     * test position with a String argument and unicode data.
     */
    public void testPositionStringWithUnicode() throws Exception {
        String[] unicodeStrings = {
            "\u0061\u0062\u0063",
            "\u0370\u0371\u0372",
            "\u05d0\u05d1\u05d2"};
        insertUnicodeData(unicodeStrings);

        runPositionStringTest();
    }

    private void runPositionStringTest() throws Exception {
        Statement stmt = createStatement();
        ResultSet rs = stmt.executeQuery("select a, b from testClob");
        int clobLength = 0;
        Clob clob;
        Random random = new Random();
        String searchString;
        int start, length, startSearchPos;
        int distance, maxStartPointDistance;
        long foundAt;
        // clobs are generated with looping alphabet streams
        maxStartPointDistance = CharAlphabet.MODERNLATINLOWER.length;
        while (rs.next()) {
            clob = rs.getClob(1);
            clobLength = rs.getInt(2);
            if (clob != null && clobLength > 0) {
                println("\n\nclobLength: " + clobLength);
                for (int i=0; i<10; i++) {
                    // find a random string to search for
                    start = Math.max(random.nextInt(clobLength - 1), 1);
                    length = random.nextInt(clobLength - start) + 1;
                    println("start:" + start + " length:" + length);
                    searchString = clob.getSubString(start, length);
                    // get random position to start the search from
                    distance = random.nextInt(maxStartPointDistance);
                    startSearchPos = Math.max((start - distance), 1);
                    // make sure that the searched string does not happen
                    // before the expected position
                    String tmp = clob.getSubString(startSearchPos, start);
                    if (tmp.indexOf(searchString) != -1) {
                        startSearchPos = start;
                    }
                    println("startSearchPos: " + startSearchPos +
                            "searchString: " + searchString);
                    foundAt = clob.position(searchString, startSearchPos);
                    assertEquals("FAIL - wrong match found for " +
                            searchString + " start at " + startSearchPos +
                            " with length " + searchString.length(),
                            start, foundAt);
                }
            }
        }
        rs.close();
        stmt.close();
    }

    /**
     * test position with a Clob argument
     */
    public void testPositionClob() throws Exception {
        insertDefaultData();

        runPositionClobTest();
    }

    /**
     * test position with a Clob argument containing unicode characters
     */
    public void testPositionClobWithUnicode() throws Exception {
        String[] unicodeStrings = {
            "\u0061\u0062\u0063",
            "\u0370\u0371\u0372",
            "\u05d0\u05d1\u05d2"};
        insertUnicodeData(unicodeStrings);

        runPositionClobTest();
    }

    private void runPositionClobTest() throws Exception {
        Statement stmt = createStatement();
        ResultSet rs = stmt.executeQuery("select a, b from testClob");
        int clobLength = 0;
        Clob clob;
        Statement stmt2 = createStatement();
        Random random = new Random();
        String searchString;
        int start, length, startSearchPos;
        int distance, maxStartPointDistance;
        long foundAt;
        // clobs are generated with looping alphabet streams
        maxStartPointDistance = CharAlphabet.MODERNLATINLOWER.length;
        while (rs.next()) {
            clob = rs.getClob(1);
            clobLength = rs.getInt(2);
            if (clob != null && clobLength > 0) {
                println("\n\nclobLength: " + clobLength);
                // Create a table with clobs to search
                stmt2.executeUpdate("CREATE TABLE searchClob " +
                        "(a clob(300K), start int, position int)");
                // insert clobs into the table
                PreparedStatement ps = prepareStatement(
                        "INSERT INTO searchClob values (?, ?, ?) ");
                for (int i=0; i<10; i++) {
                    // find a random string to search for
                    start = Math.max(random.nextInt(clobLength - 1), 1);
                    length = random.nextInt(clobLength - start) + 1;
                    println("start:" + start + " length:" + length);
                    searchString = clob.getSubString(start, length);
                    // get random position to start the search from
                    distance = random.nextInt(maxStartPointDistance);
                    startSearchPos = Math.max((start - distance), 1);
                    // make sure that the searched string does not happen
                    // before the expected position
                    String tmp = clob.getSubString(startSearchPos, start);
                    if (tmp.indexOf(searchString) != -1) {
                        startSearchPos = start;
                    }

                    ps.setString(1, searchString);
                    ps.setInt(2, startSearchPos);
                    ps.setInt(3, start);
                    ps.executeUpdate();
                }

                ps.close();

                ResultSet rs2 = stmt2.executeQuery(
                        "SELECT a, start, position FROM searchClob");
                while (rs2.next()) {
                    Clob searchClob = rs2.getClob(1);
                    startSearchPos = rs2.getInt(2);
                    start = rs2.getInt(3);

                    searchString = searchClob.getSubString(1L,
                            (int)searchClob.length());
                    println("startSearchPos: " + startSearchPos +
                            "searchString: " + searchString);
                    foundAt = clob.position(searchClob, startSearchPos);
                    assertEquals("FAIL - wrong match found for " +
                            searchString + " starting at " + startSearchPos +
                            " with length " + searchString.length(),
                            start, foundAt);
                }
                rs2.close();
                stmt2.executeUpdate("DROP TABLE searchClob");
            }
        }
        rs.close();
        stmt.close();
        stmt2.close();
    }

    /**
     * make sure clobs work for small CLOB fields also test length method
     */
    public void testSmallClobFields() throws Exception {
        Statement stmt = createStatement();
        stmt.executeUpdate(
                "ALTER TABLE testClob ADD COLUMN smallClob CLOB(10)");

        PreparedStatement ps = prepareStatement(
                "insert into testClob (smallClob) values(?)");
        String val = "";
        for (int i = 0; i < 10; i++) {
            // insert a string
            ps.setString(1, val);
            ps.executeUpdate();
            val += "x";
        }

        ResultSet rs = stmt.executeQuery("select a from testClob");
        byte[] buff = new byte[128];
        int j = 0;
        // fetch all rows back, get the columns as clobs.
        while (rs.next()) {
            // get the first column as a clob
            Clob clob = rs.getClob(1);
            if (clob != null) {
                InputStream fin = clob.getAsciiStream();
                int columnSize = 0, size = 0;
                do
                {
                    size = fin.read(buff);
                    columnSize += (size > 0) ? size : 0;
                } while (size != -1);
                assertEquals("FAIL - wrong clob size", j, columnSize);
                assertEquals("FAIL - wrong clob length", j, clob.length());
                j++;
            }
        }
        rs.close();
        stmt.close();
    }

    /**
     * make sure cannot get a clob from an int column
     */
    public void testGetClobFromIntColumn() throws Exception{

        insertDefaultData();

        Statement stmt = createStatement();

        ResultSet rs = stmt.executeQuery("select b from testClob");
        while (rs.next()) {
            try {
                Clob clob = rs.getClob(1);

                rs.close(); // Cleanup on fail
                stmt.close();

                fail("FAIL - getClob on column type int should throw " +
                        "an exception");
            } catch (SQLException se) {
                checkException(LANG_DATA_TYPE_GET_MISMATCH, se);
            }
        }
        rs.close();
        stmt.close();
    }

    /**
     * make sure setClob doesn't work on an int column
     */
    public void testSetClobToIntColumn() throws Exception {
        insertDefaultData();

        PreparedStatement ps = prepareStatement(
                "insert into testClob (b, c) values (?, ?)");
        Statement stmt = createStatement();
        ResultSet rs = stmt.executeQuery("select a, b from testClob");
        Clob clob;
        int clobLength;
        while (rs.next()) {
            // get the first ncolumn as a clob
            clob = rs.getClob(1);
            clobLength = rs.getInt(2);
            if (clob != null) {
                try {
                    ps.setClob(1,clob);
                    ps.setInt(2, clobLength);
                    ps.executeUpdate();

                    rs.close(); // Cleanup on fail
                    stmt.close();

                    fail("FAIL - can not use setClob on int column");
                } catch (SQLException se) {
                    checkException(LANG_DATA_TYPE_GET_MISMATCH, se);
                }
            }
        }
        rs.close();
        stmt.close();
    }

    /**
     * test raising of exceptions
     */
    public void testRaisingOfExceptionsClob() throws Exception {
        insertDefaultData();

        Statement stmt = createStatement();
        ResultSet rs = stmt.executeQuery(
                "select a, b from testClob WHERE a is not NULL");
        int i = 0, clobLength = 0;
        Clob clob;
        rs.next();
        clob = rs.getClob(1);
        clobLength = rs.getInt(2);
        rs.close();
        assertFalse("FAIL - clob can not be null", clob == null);

        // 0 or negative position value
        try {
            clob.getSubString(0,5);
            fail("FAIL - getSubString with 0 as position should have " +
                    "caused an exception");
        } catch (SQLException e) {
            checkException(BLOB_BAD_POSITION, e);
        }

        // negative length value
        try {
            clob.getSubString(1,-76);
            fail("FAIL - getSubString with negative length should have " +
                    "caused an exception");
        } catch (SQLException e) {
            checkException(BLOB_NONPOSITIVE_LENGTH, e);
        }
        // boundary negative 1 length
        try {
            clob.getSubString(1,-1);
            fail("FAIL - getSubString with negative length should have " +
                    "caused an exception");
        } catch (SQLException e) {
            checkException(BLOB_NONPOSITIVE_LENGTH, e);
        }
        // before start with length zero
        try {
            clob.getSubString(0,0);
            fail("FAIL - getSubString with 0 as position should have " +
                    "caused an exception");
        } catch (SQLException e) {
            checkException(BLOB_BAD_POSITION, e);
        }
        // 2 past end with length 0
        try {
            clob.getSubString(clobLength + 2,0);
            fail("FAIL - getSubString with position bigger than clob " +
                    "should have caused an exception");
        }  catch (SQLException e) {
            checkException(BLOB_POSITION_TOO_LARGE, e);
        }
        // 0 or negative position value
        try {
            clob.position("xx",-4000);
            fail("FAIL - position with negative as position should " +
                    "have caused an exception");
        } catch (SQLException e) {
            checkException(BLOB_BAD_POSITION, e);
        }
        // null pattern
        try {
            clob.position((String) null,5);
            fail("FAIL = position((String) null,5)");
        } catch (SQLException e) {
            checkException(BLOB_NULL_PATTERN_OR_SEARCH_STR, e);
        }
        // 0 or negative position value
        try {
            clob.position(clob,-42);
            fail("FAIL = position(clob,-42)");
        } catch (SQLException e) {
            checkException(BLOB_BAD_POSITION, e);
        }
        // null pattern
        try {
            clob.position((Clob) null,5);
            fail("FAIL = pposition((Clob) null,5)");
        } catch (SQLException e) {
            checkException(BLOB_NULL_PATTERN_OR_SEARCH_STR, e);
        }
    }

    /**
     * test setClob
     */
    public void testSetClob() throws Exception {
        insertDefaultData();
        ResultSet rs, rs2;
        Statement stmt = createStatement();
        stmt.execute("create table testSetClob (a CLOB(300k), b integer)");
        PreparedStatement ps = prepareStatement(
                "insert into testSetClob values(?,?)");
        rs = stmt.executeQuery("select a, b from testClob");
        Clob clob;
        int clobLength;
        while (rs.next()) {
            // get the first column as a clob
            clob = rs.getClob(1);
            clobLength = rs.getInt(2);
            if (clob != null && clobLength != 0) {
                ps.setClob(1,clob);
                ps.setInt(2,clobLength);
                ps.executeUpdate();
            }
        }
        rs.close();

        rs = stmt.executeQuery("select a, b from testSetClob");
        Clob clob2;
        int clobLength2, nullClobs = 0;
        while (rs.next()) {
            clob2 = rs.getClob(1);
            clobLength2 = rs.getInt(2);
            assertFalse("FAIL - Clob is NULL", clob2 == null);
            assertEquals("FAIL - clob.length() != clobLength",
                    clobLength2, clob2.length());
        }
        rs.close();
        stmt.executeUpdate("DROP TABLE testSetClob");
        stmt.close();
    }

    /**
     * Test Clob.position()
     */
    public void testPositionAgressiveUseOrderBy() 
        throws Exception {
            baseTestPositionAgressive(true);
    }
    public void testPositionAgressiveDoNotUseOrderBy() 
        throws Exception {
            baseTestPositionAgressive(false);
    }

    public void baseTestPositionAgressive(boolean useOrderBy) 
        throws Exception {
        Statement s = createStatement();

        s.execute("CREATE TABLE C8.T8POS" +
                "(id INT NOT NULL PRIMARY KEY, DD CLOB(1m), pos INT, L INT)");
        s.execute("CREATE TABLE C8.T8PATT(PATT CLOB(300k))");

        // characters used to fill the String
        char[] fill = new char[4];
        fill[0] = 'd';          // 1 byte UTF8 character (ASCII)
        fill[1] = '\u03a9';     // 2 byte UTF8 character (Greek)
        fill[2] = '\u0e14';     // 3 byte UTF8 character (Thai)
        fill[3] = 'j';          // 1 byte UTF8 character (ASCII)

        char[] base = new char[256 * 1024];

        for (int i = 0; i < base.length; i += 4) {

            base[i] = fill[0];
            base[i+1] = fill[1];
            base[i+2] = fill[2];
            base[i+3] = fill[3];

        }

        char[]  patternBase = new char[2 * 1024];
        for (int i = 0; i < patternBase.length; i += 8) {

            patternBase[i] = 'p';
            patternBase[i+1] = 'a';
            patternBase[i+2] = 't';
            patternBase[i+3] = '\u03aa';
            patternBase[i+4] = (char) i;// changed value to keep pattern varying
            patternBase[i+5] = 'b';
            patternBase[i+6] = 'm';
            patternBase[i+7] = '\u0e15';

        }

        PreparedStatement ps = prepareStatement(
                "INSERT INTO C8.T8POS VALUES (?, ?, ?, ?)");
        PreparedStatement psp = prepareStatement(
                "INSERT INTO C8.T8PATT VALUES (?)");

        T8insert(ps, 1, base, 256, patternBase, 8, 100, true);
        T8insert(ps, 2, base, 3988, patternBase, 8, 2045, true);
        T8insert(ps, 3, base, 16321, patternBase, 8, 4566, true);
        T8insert(ps, 4, base, 45662, patternBase, 8, 34555, true);
        T8insert(ps, 5, base, 134752, patternBase, 8, 67889, true);
        T8insert(ps, 6, base, 303, patternBase, 8, 80, false);
        T8insert(ps, 7, base, 4566, patternBase, 8, 2086, false);
        T8insert(ps, 8, base, 17882, patternBase, 8, 4426, false);
        T8insert(ps, 9, base, 41567, patternBase, 8, 31455, false);
        String pstr =
                T8insert(ps, 10, base, 114732, patternBase, 8, 87809, false);

        commit();

        psp.setString(1, pstr);
        psp.executeUpdate();

        checkClob8(s, pstr, useOrderBy);
        commit();

        ResultSet rsc = s.executeQuery("SELECT PATT FROM C8.T8PATT");
        rsc.next();
        checkClob8(s, rsc.getClob(1), useOrderBy);

        rsc.close();


        commit();

        s.execute("DELETE FROM C8.T8POS");
        s.execute("DELETE FROM C8.T8PATT");


        T8insert(ps, 1, base, 256, patternBase, 134, 100, true);
        T8insert(ps, 2, base, 3988, patternBase, 134, 2045, true);
        T8insert(ps, 3, base, 16321, patternBase, 134, 4566, true);
        T8insert(ps, 4, base, 45662, patternBase, 134, 34555, true);
        T8insert(ps, 5, base, 134752, patternBase, 134, 67889, true);
        T8insert(ps, 6, base, 303, patternBase, 134, 80, false);
        T8insert(ps, 7, base, 4566, patternBase, 134, 2086, false);
        T8insert(ps, 8, base, 17882, patternBase, 134, 4426, false);
        T8insert(ps, 9, base, 41567, patternBase, 134, 31455, false);
        pstr = T8insert(ps, 10, base, 114732, patternBase, 134, 87809, false);

        commit();
        psp.setString(1, pstr);
        psp.executeUpdate();
        commit();


        checkClob8(s, pstr, useOrderBy);
        commit();

        rsc = s.executeQuery("SELECT PATT FROM C8.T8PATT");
        rsc.next();
        checkClob8(s, rsc.getClob(1), useOrderBy);

        s.execute("DELETE FROM C8.T8POS");
        s.execute("DELETE FROM C8.T8PATT");

        T8insert(ps, 1, base, 256, patternBase, 679, 100, true);
        T8insert(ps, 2, base, 3988, patternBase, 679, 2045, true);
        T8insert(ps, 3, base, 16321, patternBase, 679, 4566, true);
        T8insert(ps, 4, base, 45662, patternBase, 679, 34555, true);
        T8insert(ps, 5, base, 134752, patternBase, 679, 67889, true);
        T8insert(ps, 6, base, 303, patternBase, 679, 80, false);
        T8insert(ps, 7, base, 4566, patternBase, 679, 2086, false);
        T8insert(ps, 8, base, 17882, patternBase, 679, 4426, false);
        T8insert(ps, 9, base, 41567, patternBase, 679, 31455, false);
        pstr = T8insert(ps, 10, base, 114732, patternBase, 679, 87809, false);

        commit();
        psp.setString(1, pstr);
        psp.executeUpdate();
        commit();


        checkClob8(s, pstr, useOrderBy);
        commit();

        rsc = s.executeQuery("SELECT PATT FROM C8.T8PATT");
        rsc.next();
        checkClob8(s, rsc.getClob(1), useOrderBy);

        s.execute("DELETE FROM C8.T8POS");
        s.execute("DELETE FROM C8.T8PATT");
        ps.close();
        psp.close();

        s.execute("DROP TABLE C8.T8POS");
        s.execute("DROP TABLE C8.T8PATT");

        s.close();

        commit();
    }

    private static String T8insert(PreparedStatement ps, int id, char[] base,
            int bl, char[] pattern, int pl, int pos, boolean addPattern)
            throws SQLException
    {

        StringBuffer sb = new StringBuffer();
        sb.append(base, 0, bl);

        // Assume the pattern looks like Abcdefgh
        // put together a block of misleading matches such as
        // AAbAbcAbcdAbcde

        int last = addPatternPrefix(sb, pattern, pl, 5, 10);

        if (last >= (pos / 2))
            pos = (last + 10) * 2;

        // now a set of misleading matches up to half the pattern width
        last = addPatternPrefix(sb, pattern, pl, pl/2, pos/2);

        if (last >= pos)
            pos = last + 13;

        // now a complete set of misleading matches
        pos = addPatternPrefix(sb, pattern, pl, pl - 1, pos);

        if (addPattern) {
            // and then the pattern
            sb.insert(pos, pattern, 0, pl);
        } else {
            pos = -1;
        }


        String dd = sb.toString();
        String pstr = new String(pattern, 0, pl);

        assertEquals("FAIL - test confused pattern not at expected location",
                pos, dd.indexOf(pstr));

        // JDBC uses 1 offset for first character
        if (pos != -1)
            pos = pos + 1;

        ps.setInt(1, id);
        ps.setString(2, dd);
        ps.setInt(3, pos);
        ps.setInt(4, dd.length());
        ps.executeUpdate();

        return pstr;

    }

    private static int addPatternPrefix(
            StringBuffer sb, char[] pattern, int pl, int fakeCount, int pos) {

        for (int i = 0; i < fakeCount && i < (pl - 1); i++) {

            sb.insert(pos, pattern, 0, i + 1);
            pos += i + 1;
        }

        return pos;
    }

    private static void checkClob8(
    Statement   s, 
    String      pstr,
    boolean     useOrderBy) 
        throws SQLException
    {
        ResultSet rs = null;

        if (useOrderBy)
        {
            rs = s.executeQuery(
                    "SELECT ID, DD, POS, L FROM C8.T8POS ORDER BY 1");
        }
        else
        {
            rs = s.executeQuery(
                    "SELECT ID, DD, POS, L FROM C8.T8POS");
        }

        while (rs.next()) {

            int id = rs.getInt(1);

            java.sql.Clob cl = rs.getClob(2);

            int pos = rs.getInt(3);
            int len = rs.getInt(4);

            long clobPosition = cl.position(pstr, 1);
            assertEquals("FAIL - position did not match",
                    (long) pos, clobPosition);
        }
        rs.close();
    }

    private static void checkClob8(
    Statement   s, 
    Clob        pstr,
    boolean     useOrderBy) 
        throws SQLException {


        ResultSet rs = null;

        if (useOrderBy)
        {
            rs = s.executeQuery(
                "SELECT ID, DD, POS, L FROM C8.T8POS ORDER BY 1");
        }
        else
        {
            rs = s.executeQuery(
                "SELECT ID, DD, POS, L FROM C8.T8POS");
        }

        while (rs.next()) {

            int id = rs.getInt(1);

            java.sql.Clob cl = rs.getClob(2);

            int pos = rs.getInt(3);
            int len = rs.getInt(4);

            long clobPosition = cl.position(pstr, 1);
            assertEquals("FAIL - position did not match",
                    (long) pos, clobPosition);

        }
        rs.close();
    }

    /**
     * make sure clob is still around after we go to the next row,
     * after we close the result set, and after we close the statement
     */
    public void testClobAfterClose() throws Exception {
        insertDefaultData();

        Statement stmt = createStatement();
        ResultSet rs = stmt.executeQuery("select a, b from testClob");
        byte[] buff = new byte[128];
        Clob[] clobArray = new Clob[10];
        int[] clobLengthArray = new int[10];
        int j = 0;
        while (rs.next()) {
            clobArray[j] = rs.getClob(1);
            clobLengthArray[j++] = rs.getInt(2);
        }
        rs.close();
        stmt.close();

        for (int i = 0; i < 10; i++) {
            if (clobArray[i] != null) {
                InputStream fin = clobArray[i].getAsciiStream();
                int columnSize = 0;
                for (;;) {
                    int size = fin.read(buff);
                    if (size == -1)
                        break;
                    columnSize += size;
                }
                assertEquals("FAIL - wrong column size",
                        columnSize, clobLengthArray[i]);
                assertEquals("FAIL - wrong column size",
                        columnSize, clobArray[i].length());
            }
        }
    }

    /**
     * test locking
     */
    public void testLockingClob() throws Exception {
        insertDefaultData();

        Statement stmt = createStatement();
        ResultSet rs = stmt.executeQuery("select a, b from testClob");
        // fetch row back, get the column as a clob.
        Clob clob = null, shortClob = null;
        int clobLength;
        while (rs.next()) {
            clobLength = rs.getInt(2);
            if (clobLength == 10000)
                clob = rs.getClob(1);
            if (clobLength == 26)
                shortClob = rs.getClob(1);
        }
        rs.close();
        stmt.close();

        assertTrue("shortClob is null", shortClob != null);
        assertTrue("clob is null", clob != null);

        Connection conn2 = openDefaultConnection();
        // turn off autocommit, otherwise blobs/clobs cannot hang around
        // until end of transaction
        conn2.setAutoCommit(false);
        // update should go through since we don't get any locks on clobs
        // that are not long columns
        Statement stmt2 = conn2.createStatement();
        stmt2.executeUpdate("update testClob set a = 'foo' where b = 26");
        assertEquals("FAIL: clob length changed", 26, shortClob.length());
        // should timeout waiting for the lock to do this
        try {
            stmt2.executeUpdate(
                    "update testClob set b = b + 1 where b = 10000");
            stmt2.close(); // Cleanup on fail
            conn2.rollback();
            conn2.close();
            fail("FAIL: row should be locked");
        } catch (SQLException se) {
            checkException(LOCK_TIMEOUT, se);
        }

        // DERBY-3740, the reference below to clob must remain after the above
        // expected lock timeout, otherwise GC might run on the clob and
        // cause intermittent problems if the GC causes lock to be released
        // early.
        assertEquals("FAIL: clob length changed", 10000, clob.length());
        
        // Test that update goes through after the transaction is committed
        commit();
        stmt2.executeUpdate("update testClob set b = b + 1 where b = 10000");
        
        stmt2.close();
        conn2.rollback();
        conn2.close();
    }

    /**
     * test locking with a long row + long column
     */
    public void testLockingWithLongRowClob() throws Exception
    {
        ResultSet rs;
        Statement stmt, stmt2;
        stmt = createStatement();
        stmt.execute("alter table testClob add column al varchar(2000)");
        stmt.execute("alter table testClob add column bl varchar(3000)");
        stmt.execute("alter table testClob add column cl varchar(2000)");
        stmt.execute("alter table testClob add column dl varchar(3000)");
        stmt.execute("alter table testClob add column el CLOB(400k)");
        PreparedStatement ps = prepareStatement(
            "insert into testClob (al, bl, cl, dl, el, b) values(?,?,?,?,?,?)");
        ps.setString(1,Formatters.padString("blaaa",2000));
        ps.setString(2,Formatters.padString("tralaaaa",3000));
        ps.setString(3,Formatters.padString("foodar",2000));
        ps.setString(4,Formatters.padString("moped",3000));
        InputStream streamIn = new LoopingAlphabetStream(10000);
        ps.setAsciiStream(5, streamIn, 10000);
        ps.setInt(6, 1);
        // DERBY-4312 make sure commit() doesn't interfere here.
        commit();
        ps.executeUpdate();
        streamIn.close();
        ps.close();
        commit();

        stmt = createStatement();
        rs = stmt.executeQuery("select el from testClob");
        // fetch row back, get the column as a clob.
        Clob clob = null;
        assertTrue("FAIL - row not found", rs.next());
        clob = rs.getClob(1);
        assertTrue("FAIL - clob is null", clob != null);
        rs.close();
        stmt.close();

        Connection conn2 = openDefaultConnection();
        // turn off autocommit, otherwise blobs/clobs cannot hang around
        // until end of transaction
        conn2.setAutoCommit(false);
        // the following should timeout
        stmt2 = conn2.createStatement();
        try {
            stmt2.executeUpdate(
                    "update testClob set el = 'smurfball' where b = 1");
            stmt2.close(); // Cleanup on fail
            conn2.rollback();
            conn2.close();
            fail("FAIL - statement should timeout");
        } catch (SQLException se) {
            checkException(LOCK_TIMEOUT, se);
        }
                
        // Test that update goes through after the transaction is committed
        commit();
        stmt2.executeUpdate("update testClob set el = 'smurfball' where b = 1");
        
        stmt2.close();
        conn2.commit();
        conn2.close();
    }

    /**
     * test accessing clob after commit
     */
    public void testClobAfterCommit() throws Exception {
        insertDefaultData();

        Statement stmt = createStatement();
        ResultSet rs = stmt.executeQuery("select a,b from testClob");
        // fetch row back, get the column as a clob.
        Clob clob = null, shortClob = null;
        int clobLength;
        int i = 0;
        while (rs.next()) {
            clobLength = rs.getInt(2);
            if (clobLength == 10000)
                clob = rs.getClob(1);
            if (clobLength == 26)
                shortClob = rs.getClob(1);
        }
        
        /*
        * We call it before the commit(); to cache the result
        * DERBY-3574
        */
        clob.length();
        shortClob.length();
        
        rs.close();
        stmt.close();
        commit();
        assertTrue("FAIL - shortClob is NULL", shortClob != null);
        // this should give blob/clob unavailable exceptions on client
        try {
            shortClob.length();
            //Should have thrown an SQLException in the
            fail("FAIL - should not be able to access Clob after commit");
        } catch (SQLException e) {
            //The same SQLState String INVALID_LOB
            //is used for LOB's(Both Clob and Blob). Ensure that
            //we get the expected exception by comparing the SQLState.
            checkException(INVALID_LOB, e);
        }

        // these should all give blob/clob data unavailable exceptions
        try {
            clob.length();
            //Large Clobs not accessible after commit. 
            //Should have thrown an SQLException here.
            fail("FAIL - should not be able to access large Clob after commit");
        } catch (SQLException e) {
            //The same SQLState String INVALID_LOB
            //is used for LOB's(Both Clob and Blob). Ensure that
            //we get the expected exception by comparing the SQLState.
            checkException(INVALID_LOB, e);
        }
        try {
            clob.getSubString(2,3);
            //Large Clobs are not accessible after commit. 
            //Should have thrown an SQLException here.
            fail("FAIL - should not be able to access large Clob after commit");
        } catch (SQLException e) {
            //The same SQLState String INVALID_LOB
            //is used for LOB's(Both Clob and Blob). Ensure that
            //we get the expected exception by comparing the SQLState.
            checkException(INVALID_LOB, e);
        }
        try {
            clob.getAsciiStream();
            //Large Clobs are not accessible after commit. 
            //Should have thrown an SQLException here.
            fail("FAIL - should not be able to access large Clob after commit");
        } catch (SQLException e) {
            //The same SQLState String INVALID_LOB
            //is used for LOB's(Both Clob and Blob). Ensure that
            //we get the expected exception by comparing the SQLState.
            checkException(INVALID_LOB, e);
        }
        try {
            clob.position("foo",2);
            //Large Clobs are not accessible after commit. 
            //Should have thrown an SQLException here.
            fail("FAIL - should not be able to access large Clob after commit");
        } catch (SQLException e) {
            //The same SQLState String INVALID_LOB
            //is used for LOB's(Both Clob and Blob). Ensure that
            //we get the expected exception by comparing the SQLState.
            checkException(INVALID_LOB, e);
        }
        try {
            clob.position(clob,2);
            //Large Clobs are not accessible after commit. 
            //Should have thrown an SQLException here.
            fail("FAIL - should not be able to access large Clob after commit");
        } catch (SQLException e) {
            //The same SQLState String INVALID_LOB
            //is used for LOB's(Both Clob and Blob). Ensure that
            //we get the expected exception by comparing the SQLState.
            checkException(INVALID_LOB, e);
        }
    }

    /**
     * test accessing clob after closing the connection
     */
    public void testClobAfterClosingConnection() throws Exception {
        insertDefaultData();

        Statement stmt = createStatement();
        ResultSet rs = stmt.executeQuery("select a,b from testClob");
        // fetch row back, get the column as a clob.
        Clob clob = null, shortClob = null;
        int clobLength;
        while (rs.next()) {
            clobLength = rs.getInt(2);
            if (clobLength == 10000)
                clob = rs.getClob(1);
            if (clobLength == 26)
                shortClob = rs.getClob(1);
        }
		
        /*
         * We call it before the commit(); to cache the result
         * DERBY-3574
         */
        clob.length();
        shortClob.length();
		
        rs.close();
        stmt.close();
        commit();
        getConnection().close();

        try {
            long len = shortClob.length();
            //Clobs on the Embedded side and the NetworkClient
            //side are not accessible after closing the
            //connection. Should have thrown an SQLException here.
            fail("FAIL - should not be able to access Clob after " +
                    "Connection Close");
        }
        catch (SQLException e) {
            //Ensure that we get the expected exception by comparing
            //the SQLState.
            checkException(NO_CURRENT_CONNECTION, e);
        }

        // these should all give blob/clob data unavailable exceptions
        try {
            clob.length();
            //Large Clobs on the Embedded side and the NetworkClient
            //side are not accessible after Connection Close. Should
            //have thrown an SQLException here.
            fail("FAIL - should not be able to access large " +
                    "Clob after Connection Close");
        } catch (SQLException e) {
            //Ensure that we get the expected exception by comparing
            //the SQLState.
            checkException(NO_CURRENT_CONNECTION, e);
        }
        try {
            clob.getSubString(2,3);
            //Large Clobs on the Embedded side and the NetworkClient
            //side are not accessible after Connection Close. Should
            //have thrown an SQLException here.
            fail("FAIL - should not be able to access large " +
                    "Clob after Connection Close");
        } catch (SQLException e) {
            //Ensure that we get the expected exception by comparing
            //the SQLState.
            checkException(NO_CURRENT_CONNECTION, e);
        }
        try {
            clob.getAsciiStream();
            //Large Clobs on the Embedded side and the NetworkClient
            //side are not accessible after Connection Close. Should
            //have thrown an SQLException here.
            fail("FAIL - should not be able to access large " +
                    "Clob after Connection Close");
        } catch (SQLException e) {
            //Ensure that we get the expected exception by comparing
            //the SQLState.
            checkException(NO_CURRENT_CONNECTION, e);
        }
        try {
            clob.position("foo",2);
            //Large Clobs on the Embedded side and the NetworkClient
            //side are not accessible after Connection Close. Should
            //have thrown an SQLException here.
            fail("FAIL - should not be able to access large " +
                    "Clob after Connection Close");
        } catch (SQLException e) {
            //Ensure that we get the expected exception by comparing
            //the SQLState.
            checkException(NO_CURRENT_CONNECTION, e);
        }
        try {
            clob.position(clob,2);
            //Large Clobs on the Embedded side and the NetworkClient
            //side are not accessible after Connection Close. Should
            //have thrown an SQLException here.
            fail("FAIL - should not be able to access large " +
                    "Clob after Connection Close");
        } catch (SQLException e) {
            //Ensure that we get the expected exception by comparing
            //the SQLState.
            checkException(NO_CURRENT_CONNECTION, e);
        }
    }

    /**
     * Make sure we get an error attempting to access the 
     * lob after commit.
     */
    public void testClobAfterCommitWithSecondClob() throws SQLException
    {
        getConnection().setAutoCommit(false);
        Statement s1 = createStatement();
        ResultSet rs1 = s1.executeQuery("values cast('first' as clob)");
        rs1.next();
        Clob first = rs1.getClob(1);
        rs1.close(); 
        commit();
        Statement s2 = createStatement();
        ResultSet rs2 = s2.executeQuery("values cast('second' as clob)");
        rs2.next();
        Clob second = rs2.getClob(1);
        try {
            first.getSubString(1,100);
            fail("first.getSubString should have failed because after the commit");
        } catch (SQLException se){
            assertSQLState(INVALID_LOB,se);
        }
        assertEquals("second",second.getSubString(1, 100));        
        rs2.close(); 
    }
    /**
     * Test fix for derby-1382.
     *
     * Test that the getClob() returns the correct value for the clob before and
     * after updating the clob when using result sets of type
     * TYPE_SCROLL_INSENSITIVE.
     *
     * @throws SQLException
     */
    public void testGetClobBeforeAndAfterUpdate() throws SQLException {
        String clobData = "initial clob ";
        PreparedStatement ps = prepareStatement("insert into " +
                "testClob (b, a) values (?, ?)");
        for (int i=0; i<10; i++) {
            ps.setInt(1, i);
            ps.setString(2, clobData + i);
            ps.execute();
        }
        ps.close();

        Statement scrollStmt = createStatement(
                ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_UPDATABLE);
        ResultSet rs = scrollStmt.executeQuery("SELECT b, a FROM testClob");

        String value;
        Clob c;

        rs.first();
        checkContentsBeforeAndAfterUpdatingClob(rs);
        rs.next();
        checkContentsBeforeAndAfterUpdatingClob(rs);
        rs.relative(3);
        checkContentsBeforeAndAfterUpdatingClob(rs);
        rs.absolute(7);
        checkContentsBeforeAndAfterUpdatingClob(rs);
        rs.previous();
        checkContentsBeforeAndAfterUpdatingClob(rs);
        rs.last();
        checkContentsBeforeAndAfterUpdatingClob(rs);
        rs.previous();
        checkContentsBeforeAndAfterUpdatingClob(rs);

        rs.close();
        scrollStmt.close();
    }

    private void checkContentsBeforeAndAfterUpdatingClob(ResultSet rs)
    throws SQLException {
        Clob c;
        String value, expectedValue;
        String clobData = "initial clob ";
        String updatedClobData = "updated clob ";

        c = rs.getClob(2);
        // check contents
        value = c.getSubString(1, (int)c.length());
        expectedValue = clobData + rs.getInt(1);
        assertEquals("FAIL - wrong clob value", expectedValue, value);
        // update contents
        value = updatedClobData + rs.getInt(1);
        c.setString(1, value);
        rs.updateClob(2, c);
        rs.updateRow();
        // check update values
        rs.next(); // leave the row
        rs.previous(); // go back to updated row
        c = rs.getClob(2);
        // check contents
        value = c.getSubString(1, (int)c.length());
        expectedValue = updatedClobData + rs.getInt(1);
        assertEquals("FAIL - wrong clob value", expectedValue, value);
    }

    /**
     * Test fix for derby-1421.
     *
     * Test that the getClob() returns the correct value for the blob before and
     * after updating the Clob using the method updateCharacterStream().
     *
     * @throws SQLException
     */
    public void testGetClobBeforeAndAfterUpdateStream() throws SQLException {
        String clobData = "initial clob ";
        PreparedStatement ps = prepareStatement("insert into " +
                "testClob (b, a) values (?, ?)");
        for (int i=0; i<10; i++) {
            ps.setInt(1, i);
            ps.setString(2, clobData + i);
            ps.execute();
        }
        ps.close();

        Statement scrollStmt = createStatement(
                ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_UPDATABLE);
        ResultSet rs = scrollStmt.executeQuery("SELECT b, a FROM testClob");

        rs.first();
        updateClobWithUpdateCharacterStream(rs);
        rs.next();
        updateClobWithUpdateCharacterStream(rs);
        rs.relative(3);
        updateClobWithUpdateCharacterStream(rs);
        rs.absolute(7);
        updateClobWithUpdateCharacterStream(rs);
        rs.previous();
        updateClobWithUpdateCharacterStream(rs);
        rs.last();
        updateClobWithUpdateCharacterStream(rs);
        rs.previous();
        updateClobWithUpdateCharacterStream(rs);

        rs.close();
        scrollStmt.close();
    }

    private void updateClobWithUpdateCharacterStream(ResultSet rs)
    throws SQLException {
        Clob c;
        String value, expectedValue;
        String clobData = "initial clob ";
        String updatedClobData = "updated clob ";

        c = rs.getClob(2);
        // check contents
        value = c.getSubString(1, (int)c.length());
        expectedValue = clobData + rs.getInt(1);
        assertEquals("FAIL - wrong clob value", expectedValue, value);

        // update contents
        value = (updatedClobData + rs.getInt(1));
        Reader updateValue = new StringReader(value);
        rs.updateCharacterStream(2, updateValue, value.length());
        rs.updateRow();
        // check update values
        rs.next(); // leave the row
        rs.previous(); // go back to updated row
        c = rs.getClob(2);
        // check contents
        value = c.getSubString(1, (int)c.length());
        expectedValue = updatedClobData + rs.getInt(1);
        assertEquals("FAIL - wrong clob value", expectedValue, value);
    }

    /**
     * test clob finalizer closes the container (should only release table and
     * row locks that are read_committed)
     * NOTE: this test does not produce output since it needs to call the
     * garbage collector whose behaviour is unreliable. It is in the test run to
     * exercise the code (most of the time).
     */
    public void testClobFinalizer() throws Exception {
        insertDefaultData();
        Statement stmt = createStatement();
        ResultSet rs = stmt.executeQuery("select a, b from testClob");

        Clob[] clobArray = new Clob[10];
        int[] clobLengthArray = new int[10];
        int j = 0;
        while (rs.next()) {
            clobArray[j] = rs.getClob(1);
            clobLengthArray[j++] = rs.getInt(2);
        }
        rs.close();
        stmt.close();

        for (int i = 0; i < 10; i++) {
            clobArray[i] = null;
        }

        System.gc();
        System.gc();
    }


    /**
     * basic test of getBinaryStream also tests length
     */
    public void testGetBinaryStream() throws Exception {
        insertDefaultData();
        Statement stmt = createStatement();
        ResultSet rs = stmt.executeQuery("select a, b, crc32 from testBlob");
        testBlobContents(rs);
        stmt.close();
        commit();
    }

    /**
     * test getBytes
     */
    public void testGetBytes() throws Exception {
        insertDefaultData();

        Statement stmt = createStatement();
        ResultSet rs = stmt.executeQuery("select a,b from testBlob");
        int blobLength = 0;
        Blob blob;
        while (rs.next()) {
            blob = rs.getBlob(1);
            blobLength = rs.getInt(2);
            if (blob != null) {
                verifyInterval(blob, 9905, 50, 0, blobLength);
                verifyInterval(blob, 5910, 150, 1, blobLength);
                verifyInterval(blob, 5910, 50, 2, blobLength);
                verifyInterval(blob, 204, 50, 3, blobLength);
                verifyInterval(blob, 68, 50, 4, blobLength);
                verifyInterval(blob, 1, 50, 5, blobLength);
                verifyInterval(blob, 1, 1, 6, blobLength);
                verifyInterval(blob, 1, 0, 7, blobLength);
                verifyInterval(blob, blobLength + 1, 0, 8, blobLength);
                if (blobLength > 100) {
                    byte[] res = blob.getBytes(blobLength-99,200);
                    assertEquals("FAIL - wrong length in bytes",
                            100, res.length);
                    // Get expected value
                    InputStream inStream = blob.getBinaryStream();
                    long left = blobLength - 100;
                    long read = 0;
                    while (left > 0 && read != -1) {
                        read = inStream.skip(Math.min(1024, left));
                        left -= read > 0? read : 0;
                    }
                    byte[] expected = new byte[100];
                    read = inStream.read(expected);
                    inStream.close();
                    assertEquals("FAIL - wrong value",
                            new String(expected),new String(res));
                }
            } else {
                assertTrue("FAIL - blob was NULL but length != 0",
                        blobLength == 0);
            }
        }
        stmt.close();
        commit();
    }

    /**
     * test position with a byte[] argument
     */
    public void testPositionBytes() throws Exception {
        insertDefaultData();

        Statement stmt = createStatement();
        ResultSet rs = stmt.executeQuery("select a, b from testBlob");
        int blobLength = 0;
        Blob blob;
        Random random = new Random();
        byte[] searchBytes;
        int start, length, startSearchPos;
        int distance, maxStartPointDistance;
        long foundAt;
        // clobs are generated with looping alphabet streams
        maxStartPointDistance = CharAlphabet.MODERNLATINLOWER.length;
        while (rs.next()) {
            blob = rs.getBlob(1);
            blobLength = rs.getInt(2);
            if (blob != null && blobLength > 0) {
                println("\n\nblobLength: " + blobLength);
                for (int i=0; i<10; i++) {
                    // find a random string to search for
                    start = Math.max(random.nextInt(blobLength - 1), 1);
                    length = random.nextInt(blobLength - start) + 1;
                    println("start:" + start + " length:" + length);
                    searchBytes = blob.getBytes(start, length);
                    String searchString = new String(searchBytes, "US-ASCII");
                    // get random position to start the search from
                    distance = random.nextInt(maxStartPointDistance);
                    startSearchPos = Math.max((start - distance), 1);
                    // make sure that the searched string does not happen
                    // before the expected position
                    byte[] tmp = blob.getBytes(startSearchPos, start);
                    if (new String(tmp,"US-ASCII").indexOf(searchString) != -1)
                    {
                        startSearchPos = start;
                    }
                    println("startSearchPos: " + startSearchPos +
                            "searchString: " + new String(searchBytes));
                    foundAt = blob.position(searchBytes, startSearchPos);
                    assertEquals("FAIL - wrong match found for " +
                            searchString + " starting at " + startSearchPos +
                            " and length of " + searchBytes.length,
                            start, foundAt);

                }
            }
        }
        rs.close();
        stmt.close();
    }

    /**
     * Tests the {@code Blob.position} using a deterministic sequence of
     * actions and arguments.
     */
    public void testPositionBlobDeterministic()
            throws IOException, SQLException {
        getConnection().setAutoCommit(false);
        final int size = 100000;
        PreparedStatement ps = prepareStatement(
                "INSERT INTO testBlob (a, b) VALUES (?, ?)");
        ps.setBinaryStream(1, new LoopingAlphabetStream(size), size);
        ps.setInt(2, size);
        ps.executeUpdate();
        ps.close();
        ps = prepareStatement("select a from testBlob where b = ?");
        ps.setInt(1, size);
        ResultSet rs = ps.executeQuery();
        assertTrue("No data found", rs.next());
        Blob blob = rs.getBlob(1);
        // Try with a one-byte pattern.
        byte[] pattern = new byte[] {(byte)'k'}; // k number 11 in the alphabet
        assertEquals(11, blob.position(pattern, 1));
        // Try with a non-existing pattern.
        pattern = new byte[] {(byte)'p', (byte)'o'};
        assertEquals(-1, blob.position(pattern, size / 3));

        // Loop through all matches 
        pattern = new byte[] {(byte)'d', (byte)'e'};
        long foundAtPos = 1;
        int index = 0;
        int stepSize = ByteAlphabet.modernLatinLowercase().byteCount();
        while ((foundAtPos = blob.position(pattern, foundAtPos +1)) != -1) {
            assertEquals((stepSize * index++) + 4, foundAtPos);
            byte[] fetchedPattern = blob.getBytes(foundAtPos, pattern.length);
            assertTrue(Arrays.equals(pattern, fetchedPattern));
        }

        // Try a longer pattern.
        int pSize = 65*1024; // 65 KB
        pattern = new byte[pSize];
        assertEquals(pSize, new LoopingAlphabetStream(pSize).read(pattern));
        assertEquals(1, blob.position(pattern, 1));
        assertEquals(stepSize * 100 +1,
                blob.position(pattern, stepSize * 99 + 7));
        // Try again after getting the length.
        assertEquals(size, blob.length());
        assertEquals(stepSize * 100 +1,
                blob.position(pattern, stepSize * 99 + 7));

        // Try specifing a starting position that's too big.
        try {
            blob.position(pattern, size*2);
            fail("Accepted position after end of Blob");
        } catch (SQLException sqle) {
            assertSQLState("XJ076", sqle);
        }

        // Fetch the last 5 bytes, try with a partial match at the end.
        byte[] blobEnd = blob.getBytes(size - 4, 5);
        pattern = new byte[6];
        System.arraycopy(blobEnd, 0, pattern, 0, blobEnd.length);
        pattern[5] = 'X'; // Only lowercase in the looping alphabet stream.
        assertEquals(-1, blob.position(pattern, size - 10));

        // Get the very last byte, try with a partial match at the end.
        blobEnd = blob.getBytes(size, 1);
        pattern = new byte[] {blobEnd[0], 'X'};
        assertEquals(-1, blob.position(pattern, size - 5));
    }

    /**
     * Test Blob.position() with blob argument
     */
    public void testPositionBlob() throws Exception {
        insertDefaultData();
        Statement stmt = createStatement();
        ResultSet rs = stmt.executeQuery("select a, b from testBlob");
        int blobLength = 0;
        Blob blob;
        Statement stmt2 = createStatement();
        Random random = new Random();
        String searchString;
        int start, length, startSearchPos;
        int distance, maxStartPointDistance;
        long foundAt;
        // clobs are generated with looping alphabet streams
        maxStartPointDistance = CharAlphabet.MODERNLATINLOWER.length;
        while (rs.next()) {
            blob = rs.getBlob(1);
            blobLength = rs.getInt(2);
            if (blob != null && blobLength > 0) {
                println("\n\nblobLength: " + blobLength);
                // Create a table with clobs to search
                stmt2.executeUpdate("CREATE TABLE searchBlob " +
                        "(a Blob(300K), start int, position int)");
                // insert clobs into the table
                PreparedStatement ps = prepareStatement(
                        "INSERT INTO searchBlob values (?, ?, ?) ");
                for (int i=0; i<10; i++) {
                    // find a random string to search for
                    start = Math.max(random.nextInt(blobLength - 1), 1);
                    length = random.nextInt(blobLength - start) + 1;
                    println("start:" + start + " length:" + length);
                    searchString = new String(blob.getBytes(start, length),"US-ASCII");
                    // get random position to start the search from
                    distance = random.nextInt(maxStartPointDistance);
                    startSearchPos = Math.max((start - distance), 1);
                    // make sure that the searched string does not happen
                    // before the expected position
                    String tmp = new String(
                            blob.getBytes(startSearchPos, start),"US-ASCII");
                    if (tmp.indexOf(searchString) != -1) {
                        startSearchPos = start;
                    }

                    ps.setBytes(1, searchString.getBytes("US-ASCII"));
                    ps.setInt(2, startSearchPos);
                    ps.setInt(3, start);
                    ps.executeUpdate();
                }

                ps.close();

                ResultSet rs2 = stmt2.executeQuery(
                        "SELECT a, start, position FROM searchBlob");
                while (rs2.next()) {
                    Blob searchBlob = rs2.getBlob(1);
                    startSearchPos = rs2.getInt(2);
                    start = rs2.getInt(3);

                    searchString = new String(
                            searchBlob.getBytes(1L, (int)searchBlob.length()),"US-ASCII");
                    println("startSearchPos: " + startSearchPos +
                            "searchString: " + searchString);
                    foundAt = blob.position(searchBlob, startSearchPos);
                    assertEquals("FAIL - wrong match found for " +
                            searchString + " starting at " + startSearchPos +
                            " and length of " + searchString.length(),
                            start, foundAt);
                }
                rs2.close();
                stmt2.executeUpdate("DROP TABLE searchBlob");
            }
        }
        rs.close();
        stmt.close();
        stmt2.close();
    }

    /**
     * Test triggers on BLOB columns.
     */
    public void testTriggerWithBlobColumn() throws Exception {
        insertDefaultData();

        Statement stmt = createStatement();
        stmt.executeUpdate("CREATE TABLE blobTest8TriggerA " +
                "(a BLOB(400k), b int, crc32 BIGINT)");
        stmt.executeUpdate("CREATE TABLE blobTest8TriggerB " +
                "(a BLOB(400k), b int, crc32 BIGINT)");
        stmt.executeUpdate(
                "create trigger T8A after update on testBlob " +
                "referencing new as n old as o " +
                "for each row "+
                "insert into blobTest8TriggerA(a, b, crc32) " +
                "values (n.a, n.b, n.crc32)");
        stmt.executeUpdate(
                "create trigger T8B after INSERT on blobTest8TriggerA " +
                "referencing new table as n " +
                "for each statement "+
                "insert into blobTest8TriggerB(a, b, crc32) " +
                "select n.a, n.b, n.crc32 from n");

        commit();
        ResultSet rs = stmt.executeQuery(
                "select a,b,crc32 from blobTest8TriggerA");
        assertFalse("FAIL - Table blobTest8TriggerA should contain no rows",
                rs.next());
        rs.close();
        commit();
        stmt.executeUpdate("UPDATE testBlob set b = b + 0");
        commit();
        rs = stmt.executeQuery(
                "select a,b,crc32 from blobTest8TriggerA");
        testBlobContents(rs);
        rs.close();
        commit();

        rs = stmt.executeQuery(
                "select a,b,crc32 from blobTest8TriggerB");
        testBlobContents(rs);
        rs.close();
        commit();
        stmt.executeUpdate("DROP TRIGGER T8A");
        stmt.executeUpdate("DROP TRIGGER T8B");
        stmt.executeUpdate("DROP TABLE blobTest8TriggerB");
        stmt.executeUpdate("DROP TABLE blobTest8TriggerA");

        stmt.close();
        commit();

    }

    /**
     * tests small blob abd length method
     */
    public void testVarbinary() throws Exception{

        Statement stmt = createStatement();
        stmt.execute("ALTER TABLE testBlob ADD COLUMN smallBlob blob(13)");

        PreparedStatement ps = prepareStatement(
                "insert into testBlob (smallBlob) values (?)");
        String val = "";

        for (int i = 0; i < 10; i++) {
            // insert a string
            ps.setBytes(1, val.getBytes("US-ASCII"));
            ps.executeUpdate();
            val = val.trim() + "x";
        }

        ResultSet rs = stmt.executeQuery("select smallBlob from testBlob");
        byte[] buff = new byte[128];
        int j = 0;
        // fetch all rows back, get the columns as clobs.
        while (rs.next()) {
            // get the first column as a clob
            Blob blob = rs.getBlob(1);
            assertTrue("FAIL - blob is null", blob != null);
            InputStream fin = blob.getBinaryStream();
            int columnSize = 0;
            for (;;) {
                int size = fin.read(buff);
                if (size == -1)
                    break;
                columnSize += size;
            }
            assertEquals("FAIL - unexpected blob size", j, columnSize);
            assertEquals("FAIL - unexpected blob length", j, blob.length());
            j++;
        }
        ps.close();
        stmt.close();
        commit();
    }

    /**
     * make sure cannot get a blob from an int column
     */
    public void testGetBlobFromIntColumn() throws Exception {

        insertDefaultData();
        Statement stmt = createStatement();

        ResultSet rs = stmt.executeQuery("select b from testClob");
        while (rs.next()) {
            // get the first column as a clob
            try {
                Blob blob = rs.getBlob(1);
                rs.close();
                stmt.close();
                fail("FAIL - getBlob on int column should throw an " +
                        "exception");
            } catch (SQLException se) {
                checkException(LANG_DATA_TYPE_GET_MISMATCH, se);
            }
        }
        rs.close();
        stmt.close();
        commit();

    }

    /**
     * make sure setBlob doesn't work for an int column
     */
    public void testSetBlobOnIntColumn() throws Exception {
        insertDefaultData();

        Statement stmt = createStatement();
        PreparedStatement ps = prepareStatement(
                "insert into testBlob (b) values(?)");
        ResultSet rs = stmt.executeQuery("select a,b from testBlob");
        Blob blob;
        int blobLength;
        while (rs.next()) {
            // get the first column as a blob
            blob = rs.getBlob(1);
            if (blob != null) {
                try {
                    ps.setBlob(1,blob);
                    ps.executeUpdate();
                    rs.close();
                    stmt.close();
                    ps.close();
                    fail("FAIL - setBlob worked on INT column");
                } catch (SQLException se) {
                    checkException(LANG_DATA_TYPE_GET_MISMATCH, se);
                }
            }
        }
        rs.close();
        stmt.close();
        ps.close();
        commit();
    }

    /**
     * test raising of exceptions
     */
    public void testRaisingOfExceptionsBlob() throws Exception {
        insertDefaultData();

        Statement stmt = createStatement();
        ResultSet rs = stmt.executeQuery("select a,b from testBlob");

        int blobLength = 0;
        Blob blob;
        while (rs.next()) {
            blob = rs.getBlob(1);
            blobLength = rs.getInt(2);
            if (blob != null) {

                // 0 or negative position value
                try {
                    blob.getBytes(0, 5);
                    fail("FAIL - getBytes with 0 as position should " +
                            "have caused an exception");
                } catch (SQLException e) {
                    checkException(BLOB_BAD_POSITION, e);
                }
                // negative length value
                try {
                    blob.getBytes(1, -76);
                    fail("FAIL - getBytes with negative length should " +
                            "have caused an exception");
                } catch (SQLException e) {
                    checkException(BLOB_NONPOSITIVE_LENGTH, e);
                }
                // zero length value
                try {
                    blob.getBytes(1, -1);
                    fail("FAIL - getBytes with negative length should " +
                            "have caused an exception");
                } catch (SQLException e) {
                    checkException(BLOB_NONPOSITIVE_LENGTH, e);
                }
                // before begin length 0
                try {
                    blob.getBytes(0, 0);
                    fail("FAIL - getBytes with 0 position and length " +
                            "should have caused an exception");
                } catch (SQLException e) {
                    checkException(BLOB_BAD_POSITION, e);
                }
                // after end length 0
                try {
                    blob.getBytes(blobLength + 2, 0);
                    fail("FAIL - getBytes with position larger than " +
                            "the length of the blob should have caused an " +
                            "exception");
                } catch (SQLException e) {
                    checkException(BLOB_POSITION_TOO_LARGE, e);
                }
                // 0 or negative position value
                try {
                    blob.position(new byte[0], -4000);
                    fail("FAIL - position with negative start " +
                            "position should have caused an exception");
                } catch (SQLException e) {
                    checkException(BLOB_BAD_POSITION, e);
                }
                // null pattern
                try {
                    blob.position((byte[]) null, 5);
                    fail("FAIL - position with null pattern should " +
                            "have caused an exception");
                } catch (SQLException e) {
                    checkException(BLOB_NULL_PATTERN_OR_SEARCH_STR, e);
                }
                // 0 or negative position value
                try {
                    blob.position(blob, -42);
                    fail("FAIL - position with negative start " +
                            "position should have caused an exception");
                } catch (SQLException e) {
                    checkException(BLOB_BAD_POSITION, e);
                }
                // null pattern
                try {
                    blob.position((Blob) null, 5);
                    fail("FAIL - position with null pattern should " +
                            "have caused an exception");
                } catch (SQLException e) {
                    checkException(BLOB_NULL_PATTERN_OR_SEARCH_STR, e);
                }
            }
        }
        rs.close();
        stmt.close();
        commit();
    }

    /**
     * test setBlob
     */
    public void testSetBlob() throws Exception {
        insertDefaultData();

        Statement stmt = createStatement();
        stmt.execute("create table testBlobX (a blob(300K), b integer)");
        PreparedStatement ps = prepareStatement(
                "insert into testBlobX values(?,?)");
        ResultSet rs = stmt.executeQuery("select a, b from testBlob");
        Blob blob;
        int blobLength;
        while (rs.next()) {
            // get the first column as a blob
            blob = rs.getBlob(1);
            blobLength = rs.getInt(2);
            if (blob != null) {
                ps.setBlob(1,blob);
                ps.setInt(2,blobLength);
                ps.executeUpdate();
            }
        }
        rs.close();
        commit();

        rs = stmt.executeQuery("select a,b from testBlobX");
        Blob blob2;
        int blobLength2;
        while (rs.next()) {
            // get the first column as a blob
            blob2 = rs.getBlob(1);
            blobLength2 = rs.getInt(2);
            assertTrue("FAIL - blob is NULL", blob2 != null);
            assertEquals("FAIL - wrong blob length",
                    blob2.length(), blobLength2);
        }
        rs.close();

        stmt.executeUpdate("DROP TABLE testBlobX");
        stmt.close();
        commit();
    }

    /**
     * make sure blob is still around after we go to the next row,
     * after we close the result set, and after we close the statement
     */
    public void testBlobAfterClose() throws Exception {
        insertDefaultData();

        Statement stmt = createStatement();
        ResultSet rs = stmt.executeQuery("select a,b from testBlob");
        byte[] buff = new byte[128];
        Blob[] blobArray = new Blob[10];
        int[] blobLengthArray = new int[10];
        int j = 0;
        while (rs.next()) {
            blobArray[j] = rs.getBlob(1);
            blobLengthArray[j++] = rs.getInt(2);
        }
        rs.close();
        stmt.close();

        for (int i = 0; i < 10; i++) {
            if (blobArray[i] != null) {
                InputStream fin = blobArray[i].getBinaryStream();
                int columnSize = 0;
                for (;;) {
                    int size = fin.read(buff);
                    if (size == -1)
                        break;
                    columnSize += size;
                }
                assertEquals("FAIL - invalid length",
                        blobLengthArray[i], columnSize);
                assertEquals("FAIL - invalid length",
                        columnSize, blobArray[i].length());
            }
        }
    }

    /**
     * test locking
     */
    public void testLockingBlob() throws Exception {

        Statement stmt = createStatement();

        insertDefaultData();

        // for blob lock to remain, autocommit must be false.
        assertFalse(getConnection().getAutoCommit());

        // test depends on small page size, make sure size is 4k
        checkSmallPageSize(stmt, "TESTBLOB");

        ResultSet rs = stmt.executeQuery("select a,b from testBlob");
        // fetch row back, get the column as a blob.
        Blob blob = null, shortBlob = null;
        int blobLength;
        while (rs.next()) {
            blobLength = rs.getInt(2);
            if (blobLength == 10000)
                blob = rs.getBlob(1);
            if (blobLength == 26)
                shortBlob = rs.getBlob(1);
        }
        rs.close();


        Connection conn2 = openDefaultConnection();
        // turn off autocommit, otherwise blobs/clobs cannot hang around
        // until end of transaction
        conn2.setAutoCommit(false);
        // Update should go through since we don't get any locks on blobs
        // that are not long columns
        Statement stmt2 = conn2.createStatement();
        stmt2.executeUpdate("update testBlob set a = null where b = 26");
        assertEquals("FAIL - blob was updated", 26, shortBlob.length());

        // should timeout waiting for the lock to do this
        try {
            
            stmt2.executeUpdate(
                    "update testBlob set b = b + 1 where b = 10000");
            stmt.close();
            stmt2.close();
            conn2.rollback();
            conn2.close();
            fail("FAIL - should have gotten lock timeout");
        } catch (SQLException se) {
            checkException(LOCK_TIMEOUT, se);
        }

        // DERBY-3740, add a reference to the retrieved blobs that is used
        // after the expected lock timeout in conn2.  Before this change 
        // this test would intermittently fail.  I believe that a smart
        // JVM/JIT recognized that the blob reference was no longer used
        // after the above while loop, and allowed gc on it before the routine
        // could get to the expected conflicting lock.  Upon GC the blob's
        // finalize code closes the internal stream and releases the lock in
        // read committed mode.

        // make sure we got the 10000 byte blob which should be a stream.
        assertTrue(blob != null);

        // make sure we got the 26 byte blob which should be materialized.
        assertTrue(shortBlob != null);
        
        // Test that update goes through after the transaction is committed
        commit();
        stmt2.executeUpdate("update testBlob set b = b + 1 where b = 10000");
        
        stmt.close();
        stmt2.close();
        conn2.commit();
        conn2.close();
    }

    /**
     * test locking with a long row + long column
     */
    public void testLockingWithLongRowBlob() throws Exception
    {
        ResultSet rs;
        Statement stmt, stmt2;

        // for blob lock to remain, autocommit must be false.
        assertFalse(getConnection().getAutoCommit());

        stmt = createStatement();

        // test depends on small page size, make sure size is 4k
        checkSmallPageSize(stmt, "TESTBLOB");

        stmt.execute("alter table testBlob add column al varchar(2000)");
        stmt.execute("alter table testBlob add column bl varchar(3000)");
        stmt.execute("alter table testBlob add column cl varchar(2000)");
        stmt.execute("alter table testBlob add column dl varchar(3000)");
        stmt.execute("alter table testBlob add column el BLOB(400k)");
        PreparedStatement ps = prepareStatement(
            "insert into testBlob (al, bl, cl, dl, el, b) values(?,?,?,?,?,?)");
        ps.setString(1,Formatters.padString("blaaa",2000));
        ps.setString(2,Formatters.padString("tralaaaa",3000));
        ps.setString(3,Formatters.padString("foodar",2000));
        ps.setString(4,Formatters.padString("moped",3000));
        InputStream streamIn = new LoopingAlphabetStream(10000);
        ps.setBinaryStream(5, streamIn, 10000);
        ps.setInt(6, 1);
        ps.executeUpdate();
        streamIn.close();
        ps.close();
        commit();

        stmt = createStatement();
        rs = stmt.executeQuery("select el from testBlob");
        // fetch row back, get the column as a clob.
        Blob blob = null;
        assertTrue("FAIL - row not found", rs.next());
        blob = rs.getBlob(1);
        assertTrue("FAIL - blob is null", blob != null);
        rs.close();
        stmt.close();

        Connection conn2 = openDefaultConnection();
        // turn off autocommit, otherwise blobs/clobs cannot hang around
        // until end of transaction
        conn2.setAutoCommit(false);
        // the following should timeout
        stmt2 = conn2.createStatement();
        try {
            stmt2.executeUpdate("update testBlob set el = null where b = 1");
            stmt2.close();
            stmt.close();
            conn2.rollback();
            conn2.close();
            fail("FAIL - statement should timeout");
        } catch (SQLException se) {
            checkException(LOCK_TIMEOUT, se);
        }
        // Test that update goes through after the transaction is committed
        commit();

        // DERBY-3740, add a reference to the retrieved blobs that is used
        // after the expected lock timeout in conn2.  Before this change 
        // this test would intermittently fail.  I believe that a smart
        // JVM/JIT recognized that the blob reference was no longer used
        // after the above while loop, and allowed gc on it before the routine
        // could get to the expected conflicting lock.  Upon GC the blob's
        // finalize code closes the internal stream and releases the lock in
        // read committed mode.
        assertTrue(
            "FAIL - blob is null after expected lock timeout", blob != null);

        stmt2.executeUpdate("update testBlob set el = null where b = 1");
        
        stmt2.close();
        conn2.commit();
        stmt.close();
        conn2.close();
    }

    /**
     * test accessing blob after commit
     */
    public void testBlobAfterCommit() throws Exception {
        insertDefaultData();

        Statement stmt = createStatement();
        ResultSet rs = stmt.executeQuery("select a, b from testBlob");
        // fetch row back, get the column as a blob.
        Blob blob = null, shortBlob = null;
        int blobLength;
        while (rs.next()) {
            blobLength = rs.getInt(2);
            if (blobLength == 10000)
                blob = rs.getBlob(1);
            if (blobLength == 26)
                shortBlob = rs.getBlob(1);
        }
		
        /*
         * We call it before the commit(); to cache the result
         * DERBY-3574
         */
        blob.length();
        shortBlob.length();
		
        rs.close();
        stmt.close();
        commit();


        assertTrue("FAIL - shortBlob is NULL", shortBlob != null);
        // This should give blob/clob unavailable exceptions with both
        // client and embedded driver.
        try {
            shortBlob.length();
            fail("FAIL - should not be able to access Blob after commit");
        } catch (SQLException e) {
            checkException(INVALID_LOB, e);
        }

        assertTrue("FAIL - blob is NULL", blob != null);
        // these should all give blob/clob data unavailable exceptions
        try {
            blob.length();
            fail("FAIL - should not be able to access large Blob after commit");
        } catch (SQLException e) {
            checkException(INVALID_LOB, e);
        }
        try {
            blob.getBytes(2,3);
            fail("FAIL - should not be able to access large Blob after commit");
        } catch (SQLException e) {
            checkException(INVALID_LOB, e);
        }
        try {
            blob.getBinaryStream();
            fail("FAIL - should not be able to access large Blob after commit");
        } catch (SQLException e) {
            checkException(INVALID_LOB, e);
        }
        try {
            blob.position("foo".getBytes("US-ASCII"),2);
            fail("FAIL - should not be able to access large Blob after commit");
        } catch (SQLException e) {
            checkException(INVALID_LOB, e);
        }
        try {
            blob.position(blob,2);
            fail("FAIL - should not be able to access large Blob after commit");
        } catch (SQLException e) {
            checkException(INVALID_LOB, e);
        }
    }

    /**
     * test accessing blob after closing the connection
     */
    public void testBlobAfterClosingConnection() throws Exception {
        insertDefaultData();

        Statement stmt = createStatement();
        ResultSet rs = stmt.executeQuery("select a, b from testBlob");
        // fetch row back, get the column as a blob.
        Blob blob = null, shortBlob = null;
        int blobLength;
        while (rs.next()) {
            blobLength = rs.getInt(2);
            if (blobLength == 10000)
                blob = rs.getBlob(1);
            if (blobLength == 26)
                shortBlob = rs.getBlob(1);
        }
		
        /*
         * We call it before the commit(); to cache the result
         * DERBY-3574
         */
        blob.length();
        shortBlob.length();
		
        rs.close();
        rollback();
        getConnection().close();

        try {
            long length = shortBlob.length();                        
            fail("FAIL - should get an exception, connection is closed");
        } catch (SQLException e) {
            checkException(NO_CURRENT_CONNECTION, e);            
        }

        // these should all give blob/clob data unavailable exceptions
        try {
            blob.length();
            fail("FAIL - should not be able to access large lob " +
                    "after the connection is closed");
        } catch (SQLException e) {
            checkException(NO_CURRENT_CONNECTION, e);
        }
        try {
            blob.getBytes(2,3);
            fail("FAIL - should not be able to access large lob " +
                    "after the connection is closed");
        } catch (SQLException e) {
            checkException(NO_CURRENT_CONNECTION, e);
        }
        try {
            blob.getBinaryStream();
            fail("FAIL - should not be able to access large lob " +
                    "after the connection is closed");
        } catch (SQLException e) {
            checkException(NO_CURRENT_CONNECTION, e);
        }
        try {
            blob.position("foo".getBytes("US-ASCII"),2);
            fail("FAIL - should not be able to access large lob " +
                    "after the connection is closed");
        } catch (SQLException e) {
            checkException(NO_CURRENT_CONNECTION, e);
        }
        try {
            blob.position(blob,2);
            fail("FAIL - should not be able to access large lob " +
                    "after the connection is closed");
        } catch (SQLException e) {
            checkException(NO_CURRENT_CONNECTION, e);
        }

        // restart the connection
        getConnection().setAutoCommit(false);
    }

    /**
     * test blob finalizer closes the container
     * (should only release table and row locks that are read_committed)
     * NOTE: this test does not produce output since it needs to call the
     * garbage collector whose behaviour is unreliable. It is in the test run to
     * exercise the code (most of the time).
     */
    public void testBlobFinalizer() throws Exception {
        insertDefaultData();
        Statement stmt = createStatement();
        ResultSet rs = stmt.executeQuery("select a,b from testBlob");

        Blob[] blobArray = new Blob[10];
        int[] blobLengthArray = new int[10];
        int j = 0;
        while (rs.next()) {
            blobArray[j] = rs.getBlob(1);
            blobLengthArray[j++] = rs.getInt(2);
        }
        rs.close();
        stmt.close();

        for (int i = 0; i < 10; i++) {
            blobArray[i] = null;
        }

        System.gc();
        System.gc();

    }

    /**
     * Test fix for derby-1382.
     *
     * Test that the getBlob() returns the correct value for the blob before and
     * after updating the blob when using result sets of type
     * TYPE_SCROLL_INSENSITIVE.
     *
     * @throws Exception
     */
    public void testGetBlobBeforeAndAfterUpdate() throws Exception {
        String blobData = "initial blob ";
        PreparedStatement ps =
                prepareStatement(
                "insert into testBlob (b, a) values (?, ?)");
        for (int i=0; i<10; i++) {
            ps.setInt(1, i);
            ps.setBytes(2, (blobData + i).getBytes("US-ASCII"));
            ps.execute();
        }
        ps.close();

        Statement scrollStmt = createStatement(
                ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_UPDATABLE);
        ResultSet rs = scrollStmt.executeQuery("SELECT b, a FROM testBlob");

        rs.first();
        checkContentsBeforeAndAfterUpdatingBlob(rs);
        rs.next();
        checkContentsBeforeAndAfterUpdatingBlob(rs);
        rs.relative(3);
        checkContentsBeforeAndAfterUpdatingBlob(rs);
        rs.absolute(7);
        checkContentsBeforeAndAfterUpdatingBlob(rs);
        rs.previous();
        checkContentsBeforeAndAfterUpdatingBlob(rs);
        rs.last();
        checkContentsBeforeAndAfterUpdatingBlob(rs);
        rs.previous();
        checkContentsBeforeAndAfterUpdatingBlob(rs);

        rs.close();
        scrollStmt.close();

    }

    private void checkContentsBeforeAndAfterUpdatingBlob(ResultSet rs)
    throws SQLException, UnsupportedEncodingException {
        Blob b;
        byte[] value, expectedValue;
        String blobData = "initial blob ";
        String updatedBlobData = "updated blob ";

        b = rs.getBlob(2);
        // check contents
        value = b.getBytes(1, blobData.length() + 1);
        expectedValue = (blobData + rs.getInt(1)).getBytes("US-ASCII");
        assertTrue("FAIL - wrong blob value",
                Arrays.equals(value, expectedValue));

        // update contents
        value = (updatedBlobData + rs.getInt(1)).getBytes("US-ASCII");
        b.setBytes(1, value);
        rs.updateBlob(2, b);
        rs.updateRow();
        // check update values
        rs.next(); // leave the row
        rs.previous(); // go back to updated row
        b = rs.getBlob(2);
        // check contents
        value = b.getBytes(1, updatedBlobData.length() + 1);
        expectedValue = (updatedBlobData + rs.getInt(1)).getBytes("US-ASCII");
        assertTrue("FAIL - wrong blob value",
                Arrays.equals(value, expectedValue));
    }

    /**
     * Test fix for derby-1421.
     *
     * Test that the getBlob() returns the correct value for the blob before and
     * after updating the blob using the method updateBinaryStream().
     *
     * @throws Exception
     */
    public void testGetBlobBeforeAndAfterUpdateStream() throws Exception {

        String blobData = "initial blob ";
        PreparedStatement ps =
                prepareStatement(
                "insert into testBlob (b, a) values (?, ?)");
        for (int i=0; i<10; i++) {
            ps.setInt(1, i);
            ps.setBytes(2, (blobData + i).getBytes("US-ASCII"));
            ps.execute();
        }
        ps.close();

        Statement scrollStmt = createStatement(
                ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_UPDATABLE);
        ResultSet rs = scrollStmt.executeQuery("SELECT b, a FROM testBlob");

        rs.first();
        updateBlobWithUpdateBinaryStream(rs);
        rs.next();
        updateBlobWithUpdateBinaryStream(rs);
        rs.relative(3);
        updateBlobWithUpdateBinaryStream(rs);
        rs.absolute(7);
        updateBlobWithUpdateBinaryStream(rs);
        rs.previous();
        updateBlobWithUpdateBinaryStream(rs);
        rs.last();
        updateBlobWithUpdateBinaryStream(rs);
        rs.previous();
        updateBlobWithUpdateBinaryStream(rs);

        rs.close();
        scrollStmt.close();
    }

    private void updateBlobWithUpdateBinaryStream(ResultSet rs)
    throws SQLException, UnsupportedEncodingException {
        Blob b;
        byte[] value, expectedValue;
        String blobData = "initial blob ";
        String updatedBlobData = "updated blob ";

        b = rs.getBlob(2);
        // check contents
        value = b.getBytes(1, blobData.length() + 1);
        expectedValue = (blobData + rs.getInt(1)).getBytes("US-ASCII");
        
        assertTrue("FAIL - wrong blob value",
                Arrays.equals(value, expectedValue));

        // update contents
        value = (updatedBlobData + rs.getInt(1)).getBytes("US-ASCII");
        InputStream updateValue = new ByteArrayInputStream(value);
        rs.updateBinaryStream(2, updateValue, value.length);
        rs.updateRow();
        // check update values
        rs.next(); // leave the row
        rs.previous(); // go back to updated row
        b = rs.getBlob(2);
        // check contents
        value = b.getBytes(1, updatedBlobData.length() + 1);
        expectedValue = (updatedBlobData + rs.getInt(1)).getBytes("US-ASCII");
        assertTrue("FAIL - wrong blob value",
                Arrays.equals(value, expectedValue));
    }

    /**
     * test behaviour of system with self destructive user
     * update a long column underneath a clob
     */
    public void testSelfDestructiveClob() throws Exception {
        insertDefaultData();

        Statement stmt = createStatement();
        ResultSet rs = stmt.executeQuery(
                "select a, b from testClob where b = 10000");
        byte[] buff = new byte[128];
        // fetch row back, get the column as a clob.
        Clob clob = null;
        InputStream fin;
        int clobLength = 0, i = 0;
        assertTrue("FAIL - row not found", rs.next());
        i++;
        clobLength = rs.getInt(2);
        // get the first column as a clob
        clob = rs.getClob(1);
        assertEquals("FAIL - wrong clob length", 10000, clobLength);
        fin = clob.getAsciiStream();
        int columnSize = 0;

        PreparedStatement ps = prepareStatement(
                "update testClob set a = ? where b = 10000");
        StringBuffer foo = new StringBuffer();
        for (int k = 0; k < 1000; k++)
            foo.append('j');
        ps.setString(1,foo.toString());
        ps.executeUpdate();

        rs = stmt.executeQuery("select a from testClob where b = 10000");
        while (rs.next()) {
            int j = 1;
            String val = rs.getString(1);
            assertEquals("FAIL - invalid blob value", foo.substring(0, 50),
                    val.substring(0,50));
            j++;
        }

        while (columnSize < 11000) {
            int size = fin.read(buff);
            if (size == -1)
                break;
            columnSize += size;
        }
        assertEquals("FAIL - invalid column size", 10000, columnSize);

        assertEquals("FAIL - invalid column size", clobLength, columnSize);
        assertEquals("FAIL - invalid column size", columnSize, clob.length());

        rs.close();
        stmt.close();
    }

    /**
     * test behaviour of system with self destructive user
     * drop table and see what happens to the clob
     * expect an IOException when moving to a new page of the long column
     */
    public void testSelfDestructiveClob2() throws Exception {
        insertDefaultData();

        Statement stmt = createStatement();
        ResultSet rs = stmt.executeQuery(
                "select a,b from testClob where b = 10000");
        byte[] buff = new byte[128];
        // fetch row back, get the column as a clob.
        Clob clob = null;
        InputStream fin;
        int clobLength = 0, i = 0;
        assertTrue("FAIL - row not found", rs.next());
        i++;
        clobLength = rs.getInt(2);
        // get the first column as a clob
        clob = rs.getClob(1);
        assertEquals("FAIL - wrong clob length", 10000, clobLength);
        fin = clob.getAsciiStream();
        int columnSize = 0;

        stmt.executeUpdate("drop table testClob");

        try {
            while (columnSize < 11000) {
                int size = fin.read(buff);
                if (size == -1)
                    break;
                columnSize += size;
            }
            fail("FAIL - should have got an IOException");
        } catch (java.io.IOException ioe) {
            if(usingEmbedded()) {
                assertEquals("FAIL - wrong exception",
                        "ERROR 40XD0: Container has been closed.",
                        ioe.getMessage());
            }
        }

        rollback();
    }

    /**
     * Test fix for derby-265.
     * Test that if getBlob is called after the transaction
     * in which it was created is committed, a proper user error
     * is thrown instead of an NPE.
     * Basically per the spec, getBlob is valid only for the duration of
     * the transaction it was created in
     * Updated for DERBY-1511: The test case wasn't supposed to fail in the
     * first place (neither with NPE nor with "proper user error") since none
     * of the BLOBs are accessed after the transaction that created them was
     * completed.
     * @throws Exception
     * @throws FileNotFoundException
     * @throws IOException
     */
    public void testNegativeTestDerby265Blob() throws Exception {
        getConnection().setAutoCommit(false);

        PreparedStatement ps = prepareStatement(
                "insert into testBlob(b, a) values(?,?)");
        for (int i = 0; i < 3; i++) {
            InputStream fis = new LoopingAlphabetStream(300000);
            ps.setInt(1, i);
            ps.setBinaryStream(2, fis, 300000);
            ps.executeUpdate();
            fis.close();
        }
        commit();

        getConnection().setAutoCommit(true);

        Statement s = createStatement(ResultSet.TYPE_FORWARD_ONLY,
                ResultSet.CONCUR_READ_ONLY);
        s.execute("SELECT b, a FROM testBlob");
        ResultSet rs1 = s.getResultSet();
        Statement s2 = createStatement(ResultSet.TYPE_FORWARD_ONLY,
                ResultSet.CONCUR_READ_ONLY);
        s2.executeQuery("SELECT b, a FROM testBlob");
        ResultSet rs2 = s2.getResultSet();
        rs2.next();

        Blob b2 = rs2.getBlob(2);
        rs1.next();
        Blob b1 = rs1.getBlob(2);
        rs1.close();

        // DERBY-1511: Fetching the next BLOB here used to fail because it
        // had been pre-fetched before the commit and was closed in the commit.
        // Now, we don't pre-fetch BLOBs anymore, so expect to get a working
        // object here.
        assertTrue(rs2.next());
        assertNotNull(rs2.getBlob(2));
        rs2.close();
    }

    /**
     * Test fix for derby-265.
     * Test that if getClob is called after the transaction
     * in which it was created is committed, a proper user error
     * is thrown instead of an NPE.
     * Basically per the spec, getClob is valid only for the duration of
     * the transaction in it was created in
     * Updated for DERBY-1511: The test case wasn't supposed to fail in the
     * first place (neither with NPE nor with "proper user error") since none
     * of the CLOBs are accessed after the transaction that created them was
     * completed.
     * @throws Exception
     */
    public void testNegativeTestDerby265Clob() throws Exception {

        getConnection().setAutoCommit(false);

        PreparedStatement ps = prepareStatement(
                "insert into testClob(b, a) values(?,?)");
        for (int i = 0; i < 3; i++) {
            Reader fis = new LoopingAlphabetReader(300000);
            ps.setInt(1, i);
            ps.setCharacterStream(2, fis, 300000);
            ps.executeUpdate();
            fis.close();
        }
        commit();

        getConnection().setAutoCommit(true);

        Statement s = createStatement(ResultSet.TYPE_FORWARD_ONLY,
                ResultSet.CONCUR_READ_ONLY);
        s.execute("SELECT b, a FROM testClob");
        ResultSet rs1 = s.getResultSet();
        Statement s2 = createStatement(ResultSet.TYPE_FORWARD_ONLY,
                ResultSet.CONCUR_READ_ONLY);
        s2.executeQuery("SELECT b, a FROM testClob");
        ResultSet rs2 = s2.getResultSet();
        rs2.next();

        Clob b2 = rs2.getClob(2);
        rs1.next();
        Clob b1 = rs1.getClob(2);
        rs1.close();

        // DERBY-1511: Fetching the next CLOB here used to fail because it
        // had been pre-fetched before the commit and was closed in the commit.
        // Now, we don't pre-fetch CLOBs anymore, so expect to get a working
        // object here.
        assertTrue(rs2.next());
        assertNotNull(rs2.getClob(2));
        rs2.close();
    }

    public static Test suite() {
        BaseTestSuite suite = new BaseTestSuite("BlobClob4BlobTest");
        suite.addTest(baseSuite("embedded"));
        suite.addTest(
                TestConfiguration.clientServerDecorator(baseSuite("client")));

        // JSR169 does not have encryption support
        if (JDBC.vmSupportsJDBC3())
        {
            Test encSuite = baseSuite("encrypted");
            suite.addTest(Decorator.encryptedDatabase (encSuite));
        }

        return suite;
    }

    private static Test baseSuite(String name) {
        BaseTestSuite suite = new BaseTestSuite(
                BlobClob4BlobTest.class, "BlobClob4BlobTest:" + name);
        return new CleanDatabaseTestSetup(
                DatabasePropertyTestSetup.setLockTimeouts(suite, 2, 4));
    }


    private void insertDefaultData() throws Exception {
        PreparedStatement ps = prepareStatement(
                "INSERT INTO testClob (a, b, c) VALUES (?, ?, ?)");

        String clobValue = "";
        ps.setString(1, clobValue);
        ps.setInt(2, clobValue.length());
        ps.setLong(3, 0);
        ps.addBatch();
        clobValue = "you can lead a horse to water but you can't form it " +
                "into beverage";
        ps.setString(1, clobValue);
        ps.setInt(2, clobValue.length());
        ps.setLong(3, 0);
        ps.addBatch();
        clobValue = "a stitch in time says ouch";
        ps.setString(1, clobValue);
        ps.setInt(2, clobValue.length());
        ps.setLong(3, 0);
        ps.addBatch();
        clobValue = "here is a string with a return \n character";
        ps.setString(1, clobValue);
        ps.setInt(2, clobValue.length());
        ps.setLong(3, 0);
        ps.addBatch();

        ps.executeBatch();
        ps.clearBatch();

        insertLoopingAlphabetStreamData(
                ps, CharAlphabet.modernLatinLowercase(), 0);
        insertLoopingAlphabetStreamData(
                ps, CharAlphabet.modernLatinLowercase(), 56);
        insertLoopingAlphabetStreamData(
                ps, CharAlphabet.modernLatinLowercase(), 5000);
        insertLoopingAlphabetStreamData(
                ps, CharAlphabet.modernLatinLowercase(), 10000);
        insertLoopingAlphabetStreamData(
                ps, CharAlphabet.modernLatinLowercase(), 300000);

        ps.setNull(1, Types.CLOB);
        ps.setInt(2, 0);
        ps.setLong(3, 0);
        ps.executeUpdate();

        ps.close();

        ps = prepareStatement(
                "INSERT INTO testBlob (a, b, crc32) VALUES (?, ?, ?)");

        byte[] blobValue = "".getBytes("US-ASCII");
        ps.setBytes(1, blobValue);
        ps.setInt(2, blobValue.length);
        ps.setLong(3, getStreamCheckSum(new ByteArrayInputStream(blobValue)));
        ps.addBatch();
        blobValue = ("you can lead a horse to water but you can't form it " +
                "into beverage").getBytes("US-ASCII");
        ps.setBytes(1, blobValue);
        ps.setInt(2, blobValue.length);
        ps.setLong(3, getStreamCheckSum(new ByteArrayInputStream(blobValue)));
        ps.addBatch();
        blobValue = "a stitch in time says ouch".getBytes("US-ASCII");
        ps.setBytes(1, blobValue);
        ps.setInt(2, blobValue.length);
        ps.setLong(3, getStreamCheckSum(new ByteArrayInputStream(blobValue)));
        ps.addBatch();
        blobValue = "here is a string with a return \n character".
                getBytes("US-ASCII");
        ps.setBytes(1, blobValue);
        ps.setInt(2, blobValue.length);
        ps.setLong(3, getStreamCheckSum(new ByteArrayInputStream(blobValue)));
        ps.addBatch();

        ps.executeBatch();
        ps.clearBatch();

        insertLoopingAlphabetStreamData(
                ps, 0);
        insertLoopingAlphabetStreamData(
                ps, 56);
        insertLoopingAlphabetStreamData(
                ps, 5000);
        insertLoopingAlphabetStreamData(
                ps, 10000);
        insertLoopingAlphabetStreamData(
                ps, 300000);

        ps.setNull(1, Types.BLOB);
        ps.setInt(2, 0);
        ps.setNull(3, Types.BIGINT);
        ps.executeUpdate();

        ps.close();

        commit();
    }


    private void insertUnicodeData(String[] unicodeString) throws Exception {
        PreparedStatement ps = prepareStatement(
                "INSERT INTO testClob (a, b, c) VALUES (?, ?, ?)");

        for (int i=0; i<unicodeString.length; i++) {
            ps.setString(1, unicodeString[i]);
            ps.setInt(2, unicodeString[i].length());
            ps.setInt(3, i);
            ps.addBatch();
        }
        ps.executeBatch();
        ps.clearBatch();

        insertLoopingAlphabetStreamData(
                ps, CharAlphabet.tamil(), 0);
        insertLoopingAlphabetStreamData(
                ps, CharAlphabet.tamil(), 56);
        insertLoopingAlphabetStreamData(
                ps, CharAlphabet.tamil(), 5000);
        insertLoopingAlphabetStreamData(
                ps, CharAlphabet.tamil(), 10000);
        insertLoopingAlphabetStreamData(
                ps, CharAlphabet.tamil(), 300000);

        ps.setNull(1, Types.CLOB);
        ps.setInt(2, 0);
        ps.setInt(3, -1);
        ps.executeUpdate();

        ps.close();
        commit();
    }

    private void insertLoopingAlphabetStreamData(
            PreparedStatement ps,
            int lobLength)
        throws Exception
    {
        ps.setBinaryStream(1, new LoopingAlphabetStream(lobLength), lobLength);
        ps.setInt(2, lobLength);
        ps.setLong(3, getStreamCheckSum(new LoopingAlphabetStream(lobLength)));
        ps.executeUpdate();
    }

    private void insertLoopingAlphabetStreamData(
            PreparedStatement ps,
            CharAlphabet alphabet,
            int lobLength)
        throws Exception
    {
        ps.setCharacterStream(1, new LoopingAlphabetReader(lobLength, alphabet),
                lobLength);
        ps.setInt(2, lobLength);
        ps.setLong(3, -1);
        ps.executeUpdate();
    }

    private boolean compareReaders(Reader origValue, Reader newValue)
    throws Exception {
        char[] origBuff = new char[1024];
        char[] newBuff = new char[1024];
        int countOrig = -1;
        int countNew = -1;
        do {
            countOrig = origValue.read(origBuff);
            countNew = newValue.read(newBuff);
            if (countOrig != countNew) {
                return false;
            }
            if (!java.util.Arrays.equals(origBuff, newBuff)) {
                return false;
            }
        } while (countOrig != -1);
        return true;
    }

    /**
     * Get the CRC32 checksum of a stream, reading
     * its contents entirely and closing it.
     */
    private long getStreamCheckSum(InputStream in)
    throws Exception {
        CRC32 sum = new CRC32();

        byte[] buf = new byte[32*1024];

        for (;;) {
            int read = in.read(buf);
            if (read == -1)
                break;
            sum.update(buf, 0, read);
        }
        in.close();
        return sum.getValue();
    }

    /**
     * Verifies the value returned by the method Clob.getSubstring()
     */
    private void verifyInterval(Clob clob, long pos, int length,
            int testNum, int clobLength) throws Exception {
        try {
            String subStr = clob.getSubString(pos, length);
            assertEquals("FAIL - getSubString returned wrong length",
                    Math.min((clob.length() - pos) + 1, length),
                    subStr.length());
            assertEquals("FAIL - clob has mismatched lengths",
                    clobLength, clob.length());
            assertFalse("FAIL - NO ERROR ON getSubString POS TOO LARGE",
                    (pos > clobLength + 1));

            // Get expected value usign Clob.getAsciiStream()
            char[] value = new char[length];
            String valueString;
            Reader reader = clob.getCharacterStream();
            long left = pos - 1;
            long skipped = 0;
            if (clobLength > 0) {
                println("clobLength: " + clobLength);
                while (left > 0 && skipped >= 0) {
                    skipped = reader.skip(Math.min(1024, left));
                    left -= skipped > 0 ? skipped : 0;
                }
            }
            int numBytes = reader.read(value);

            // chech the the two values match
            if (numBytes >= 0) {
                char[] readBytes = new char[numBytes];
                System.arraycopy(value, 0, readBytes, 0, numBytes);
                valueString = new String(readBytes);
                assertEquals("FAIL - wrong substring value",
                        valueString, subStr);
            } else {
                assertTrue("FAIL - wrong length", subStr.length() == 0);
            }
        } catch (SQLException e) {
            if (pos <= 0) {
                checkException(BLOB_BAD_POSITION, e);
            } else {
                if (pos > clobLength + 1) {
                    checkException(BLOB_POSITION_TOO_LARGE, e);
                } else {
                    throw e;
                }
            }
        }
    }

    /**
     * Verifies the value returned by the method Blob.getBytes()
     */
    private void verifyInterval(Blob blob, long pos, int length,
            int testNum, int blobLength) throws Exception {
        try {
            String subStr = new String(blob.getBytes(pos,length), "US-ASCII");
            assertEquals("FAIL - getSubString returned wrong length ",
                    Math.min((blob.length() - pos) + 1, length),
                    subStr.length());
            assertEquals("FAIL - clob has mismatched lengths",
                    blobLength, blob.length());
            assertFalse("FAIL - NO ERROR ON getSubString POS TOO LARGE",
                    (pos > blobLength + 1));

            // Get expected value usign Blob.getBinaryStream()
            byte[] value = new byte[length];
            String valueString;
            InputStream inStream = blob.getBinaryStream();
            inStream.skip(pos - 1);
            int numBytes = inStream.read(value);
            // check that the two values match
            if (numBytes >= 0) {
                byte[] readBytes = new byte[numBytes];
                System.arraycopy(value, 0, readBytes, 0, numBytes);
                valueString = new String(readBytes, "US-ASCII");
                assertEquals("FAIL - wrong substring value",
                        valueString, subStr);
            } else {
                assertTrue("FAIL - wrong length", subStr.length() == 0);
            }
        } catch (SQLException e) {
            if (pos <= 0) {
                checkException(BLOB_BAD_POSITION, e);
            } else {
                if (pos > blobLength + 1) {
                    checkException(BLOB_POSITION_TOO_LARGE, e);
                } else {
                    throw e;
                }
            }
        }
    }


    /**
     * Test the contents of the testBlob table or ResultSet
     * with identical shape.
     * @param rs
     * @throws Exception
     */
    public void testBlobContents(ResultSet rs) throws Exception {
        int nullCount = 0;
        int rowCount = 0;
        byte[] buff = new byte[128];
        // fetch row back, get the long varbinary column as a blob.
        Blob blob;
        int blobLength = 0, i = 0;
        while (rs.next()) {
            i++;
            // get the first column as a clob
            blob = rs.getBlob(1);
            long crc32 = rs.getLong(3);
            boolean crc2Null = rs.wasNull();
            if (blob == null) {
                assertTrue("FAIL - NULL BLOB but non-NULL checksum", crc2Null);
                nullCount++;
            } else {
                rowCount++;

                long blobcrc32 = getStreamCheckSum(blob.getBinaryStream());
                assertEquals("FAIL - mismatched checksums for blob with " +
                        "length " + blob.length(), blobcrc32, crc32);

                InputStream fin = blob.getBinaryStream();
                int columnSize = 0;
                for (;;) {
                    int size = fin.read(buff);
                    if (size == -1)
                        break;
                    columnSize += size;
                }
                blobLength = rs.getInt(2);
                assertEquals("FAIL - wrong column size",
                        blobLength, columnSize);
                assertEquals("FAIL - wrong column length",
                        blobLength, blob.length());
            }
        }
        assertEquals("FAIL - wrong not null row count null:" + nullCount,
                9, rowCount);
        assertEquals("FAIL - wrong null blob count", 1, nullCount);
    }

    /**
     * From LobTest.java, test various inserts on a BLOB column
     * 
     * @throws SQLException
     */
    public void testBlobInsert() throws SQLException {
        String[] typeNames = { "int", "char(10)", "varchar(80)", "long varchar",
                "char(10) for bit data", "long varchar for bit data", "blob(80)" };

        Connection conn = getConnection();
        Statement s = conn.createStatement();

        // create table for testing

        s.execute("create table blobCheck (bl blob(80)) ");

        int columns = typeNames.length;
        // test insertion of literals.
        for (int i = 0; i < columns; i++) {

            if (typeNames[i].indexOf("blob") == -1)
                continue;

            // Check char literals.
            // (fail)
            String insert = "insert into blobCheck (bl"
                    + " ) values ('string' )";
            assertStatementError("42821",s,insert);
            // (succeed)
            insert = "insert into blobCheck (bl" + " ) values (cast ("
                    + Utilities.stringToHexLiteral("string") + " as blob(80)) )";
            s.execute(insert);
            // Check bit literals.
            // (fail)
            insert = "insert into blobCheck (bl" + " ) values (X'48' )";
           assertStatementError("42821",s,insert);
            // old CS compatible value: ( b'01001' )
            // (succeed)
            insert = "insert into blobCheck (bl"
                    + " ) values (cast (X'C8' as blob(80)) )";
            s.execute(insert);
            // Check hex literals.
            // (fail)
            insert = "insert into blobCheck (bl" + " ) values ( X'a78a' )";
            assertStatementError("42821",s,insert);
            // (succeed)
            insert = "insert into blobCheck (bl"
                    + " ) values (cast (X'a78a' as blob(80)) )";
            s.execute(insert);
        }
        s.execute("drop table blobCheck");
    }

     
    private void checkException(String SQLState, SQLException se)
            throws Exception
    {
         assertSQLState(SQLState, se);
    }


    /**
     * DERBY-3243 Fix ArrayIndexOutOfBounds Exception
     * if we retrieve more than 32K lobs
     * 
     */
    public void testRetrieveMoreThan32KLobs() throws SQLException
    {
        int numRows = 34000;
        // Load the database
        Connection conn = getConnection();
        conn.setAutoCommit(false);
        Statement s = createStatement();
        
        PreparedStatement ps = prepareStatement("INSERT INTO TESTCLOB VALUES(?,?,?)");
        for (int i = 0 ; i < numRows;i++)
        {
            ps.setInt(1,i);
            ps.setInt(2,i);
            ps.setString(3,"" + i);
            ps.executeUpdate();
            if (i % 1000 == 0) {
                commit();
            }
        }
        commit();
        
        // retrieve the data
        
        ResultSet rs = s.executeQuery("SELECT * from TESTCLOB");
        while (rs.next()) {
            rs.getInt(1);
            Clob c = rs.getClob(3);
            c.getSubString(1,100);
        }
        rs.close();
        
        conn.commit();
        
        
    }

    /**
     * Test that Blob.truncate() resets the position before copying the first
     * N bytes into a new holder object. The bug is only triggered if the
     * BLOB returned as a stream from store. That is, it must be larger than
     * one page so that it's not materialized. Regression test for DERBY-5113.
     */
    public void testDerby5113() throws Exception {
        setAutoCommit(false);

        // Insert a BLOB larger than one page. Normally, this means larger
        // than 32K, but the test tables use smaller pages, see comment in
        // setUp().
        PreparedStatement insert = prepareStatement(
                "insert into testblob(a) values ?");
        insert.setBinaryStream(1, new LoopingAlphabetStream(5000), 5000);
        insert.executeUpdate();

        // Retrieve the BLOB.
        Statement s = createStatement();
        ResultSet rs = s.executeQuery("select a from testblob");
        rs.next();
        Blob blob = rs.getBlob(1);

        // Now call getBytes() so that the position in the underlying stream
        // is changed.
        byte[] bytes = blob.getBytes(1, 3000);
        assertEquals(new LoopingAlphabetStream(3000),
                     new ByteArrayInputStream(bytes));

        // Truncate the BLOB. This used to fail with "Reached EOF prematurely"
        // because truncate() didn't move the position in the underlying stream
        // back to the beginning.
        blob.truncate(4000);

        // Verify that the BLOB was truncated correctly.
        assertEquals(4000, blob.length());
        bytes = blob.getBytes(1, 4000);
        assertEquals(new LoopingAlphabetStream(4000),
                     new ByteArrayInputStream(bytes));

        rs.close();
    }

    private void checkSmallPageSize(Statement st, String tblName)
        throws SQLException
    {
        ResultSet rs = st.executeQuery(
            "select * from TABLE(SYSCS_DIAG.SPACE_TABLE('"+tblName+"')) T");

        int found_rows = 0;
        while (rs.next())
        {
            found_rows++;
            assertEquals(4096, rs.getInt("pagesize"));
        }
        assertTrue(found_rows == 1);
        rs.close();
    }
        
    
    private static final String BLOB_BAD_POSITION = "XJ070";
    private static final String BLOB_NONPOSITIVE_LENGTH = "XJ071";
    private static final String BLOB_POSITION_TOO_LARGE = "XJ076";
    private static final String LANG_DATA_TYPE_GET_MISMATCH = "22005";
    private static final String BLOB_NULL_PATTERN_OR_SEARCH_STR = "XJ072";
    private static final String LOCK_TIMEOUT = "40XL1";
    private static final String NO_CURRENT_CONNECTION = "08003";
    private static final String INVALID_LOB = "XJ215";

}
