/*

Derby - Class org.apache.derbyTesting.functionTests.tests.largedata.LobLimitsTest

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.largedata;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Reader;
import java.sql.Blob;
import java.sql.Clob;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;
import junit.framework.Test;
import org.apache.derbyTesting.functionTests.util.PrivilegedFileOpsForTests;
import org.apache.derbyTesting.junit.BaseJDBCTestCase;
import org.apache.derbyTesting.junit.BaseTestSuite;
import org.apache.derbyTesting.junit.CleanDatabaseTestSetup;
import org.apache.derbyTesting.junit.JDBC;
import org.apache.derbyTesting.junit.SupportFilesSetup;
import org.apache.derbyTesting.junit.SystemPropertyTestSetup;
import org.apache.derbyTesting.junit.TestConfiguration;

/**
 * This test is part of the "largedata" suite because this test tests data for
 * lobs to the limits ( ie blob and clob can be 2G-1 maximum) and so this test
 * may take considerable disk space as well as time to run. Hence it is not part
 * of the derbyall suite but should ideally be run at some intervals to test if
 * no regression has occurred.
 */

public class LobLimitsTest extends BaseJDBCTestCase {

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

    static boolean trace = false;

    static final int _2GB = 2 * 1024 * 1024 * 1024 - 1;
    static final int _100MB = 100 * 1024 * 1024;
    static final long _4GB = 4 * 1024 * 1024 * (1024L);
    static final int NUM_TRAILING_SPACES = 33 * 1024;

    static int BIGGEST_LOB_SZ = _2GB;

    public static void setBIGGEST_LOB_SZ(int bIGGESTLOBSZ) {
        BIGGEST_LOB_SZ = bIGGESTLOBSZ;
    }

    static int BIG_LOB_SZ = _100MB;

    public static void setBIG_LOB_SZ(int bIGLOBSZ) {
        BIG_LOB_SZ = bIGLOBSZ;
    }

    static int MORE_DATA_THAN_COL_WIDTH = (_100MB) + 1;

    public static void setMORE_DATA_THAN_COL_WIDTH(int mOREDATATHANCOLWIDTH) {
        MORE_DATA_THAN_COL_WIDTH = mOREDATATHANCOLWIDTH;
    }

    static final String DATAFILE = "extinout/byteLobLimits.dat";

    static final String CHARDATAFILE = "extinout/charLobLimits.txt";

    /**
     * Setup the schema and the blob sizes for the test.
     * 
     * @param s
     * @param biggestlobsz
     * @param biglobsz
     * @throws SQLException
     */
    static void setupTables(Statement s, int biggestlobsz, int biglobsz)
            throws SQLException {
        setBIGGEST_LOB_SZ(biggestlobsz);
        setBIG_LOB_SZ(biglobsz);
        setMORE_DATA_THAN_COL_WIDTH(biglobsz + 1);
        println("BIGGEST_LOB_SZ=" + BIGGEST_LOB_SZ + " BIG_LOB_SZ="
                + BIG_LOB_SZ);
        s.execute("CREATE TABLE BLOBTBL (ID INT NOT NULL PRIMARY KEY, "
                + "POS BIGINT, DLEN BIGINT, CONTENT BLOB(2G))");
        s.execute("CREATE TABLE CLOBTBL (ID INT NOT NULL PRIMARY KEY,"
                + "POS BIGINT, DLEN BIGINT, CONTENT CLOB(2G))");
        s.execute("CREATE TABLE BLOBTBL2 (ID INT NOT NULL PRIMARY KEY, "
                + "POS BIGINT, CONTENT BLOB(" + BIG_LOB_SZ + "),DLEN BIGINT)");

        // Please dont change the clob column width,since tests use this width
        // to
        // test for truncation of trailing spaces.
        s.execute("CREATE TABLE CLOBTBL2 (ID INT NOT NULL PRIMARY KEY,"
                + "POS BIGINT, CONTENT CLOB(" + BIG_LOB_SZ + "), DLEN BIGINT)");

    }

    public static Test suite() {
        // Right now run just with embeddded.
        return baseSuite(_2GB, _100MB);
    }

    /**
     * Create an instance of the {@code LobLimitsTest} suite.
     *
     * @param biggestSize the size of the biggest LOB to test
     * @param bigSize the size of a typical big LOB to test
     * @return a test suite
     */
    static Test baseSuite(final int biggestSize, final int bigSize) {
    	//Run the suite with following properties in case we run into lock
    	// time out issues. It will help debug the problem if timeouts occur.
        Properties sysprops = new Properties();
        sysprops.setProperty("derby.locks.deadlockTrace", "true");
        sysprops.setProperty("derby.locks.monitor", "true");
        // Some of the test cases depend on certain other test cases to run
        // first, so force the test cases to run in lexicographical order.
        Test suite =
                new CleanDatabaseTestSetup(
                        new BaseTestSuite(LobLimitsTest.class)) {
            @Override
            protected void decorateSQL(Statement s)
                           throws SQLException {
                setupTables(s, biggestSize, bigSize);
            }
        };
        suite = new SystemPropertyTestSetup(suite,sysprops);

        return new SupportFilesSetup(suite);
    }

    /**
     * tests specific for blobs
     * @throws Exception
     */
    public void test_01_Blob() throws Exception {
        setAutoCommit(false);
        PreparedStatement insertBlob =
                prepareStatement("INSERT INTO BLOBTBL values (?,?,?,?)");
        PreparedStatement selectBlob =
                prepareStatement("SELECT CONTENT,DLEN FROM BLOBTBL WHERE ID = ?");
        PreparedStatement insertBlob2 =
                prepareStatement("INSERT INTO BLOBTBL2 values (?,?,?,?)");
        PreparedStatement selectBlob2 =
                prepareStatement("SELECT CONTENT,DLEN FROM BLOBTBL2 WHERE ID = ?");
        // Test - 2Gb blob ( actually it is 2gb -1)
        // Note with setBinaryStream interface the maximum size for the
        // stream, can be max value for an int.
        // Also note, that lobs in derby currently supports
        // maximum size of 2gb -1

        // first do insert blob of 2g, 2 rows
        insertBlob_SetBinaryStream("BlobTest #1", insertBlob,
                BIGGEST_LOB_SZ,
                   0, 2, BIGGEST_LOB_SZ);
        // do a select to see if the inserts in test above went ok
        selectBlob("BlobTest #2", selectBlob, BIGGEST_LOB_SZ, 0, 1);
        selectBlob("BlobTest #3", selectBlob, BIGGEST_LOB_SZ, 1, 1);

        // now do a select of one of the 2gb rows and update another 2g row
        // using the setBlob api, updated blob is of length 2gb
        // Fix for Bug entry -DERBY-599[setBlob should not materialize blob
        // into memory]
        selectUpdateBlob("BlobTest #4", selectBlob, BIGGEST_LOB_SZ, 0, 1);
        // select row from blobtbl and then do insert into the blobtbl
        // using setBlob
        selectInsertBlob("BlobTest #4.1", selectBlob, insertBlob,
                BIGGEST_LOB_SZ, 0, 3);

        // Test - generate random data, write to a file, use it to insert
        // data into blob and then read back and compare if all is ok
        // currently in fvt ( derbyall), tests check for substrings etc and
        // for small amounts of data. This test will test for 100mb of blob data

        FileOutputStream fos =
                PrivilegedFileOpsForTests
                        .getFileOutputStream(new File(DATAFILE));
        RandomByteStreamT r = new RandomByteStreamT(new java.util.Random(),
                   BIG_LOB_SZ);
        // write in chunks of 32k buffer
        byte[] buffer = new byte[32 * 1024];
        int count = 0;

        while ((count = r.read(buffer)) >= 0)
            fos.write(buffer, 0, count);

        fos.flush();
        fos.close();
        insertBlob2("BlobTest #5.1 ", insertBlob2, BIG_LOB_SZ, 0, 1,
                   BIG_LOB_SZ, DATAFILE);
        selectBlob2("BlobTest #5.2 ", selectBlob2, BIG_LOB_SZ, 0, 1,
                   DATAFILE);

        // update the 2gb row in blobtbl with the 100mb data and compare if the
        // update
        // went ok.
        selectUpdateBlob2("BlobTest #6", selectBlob2, selectBlob,
                BIG_LOB_SZ, 0, 1, DATAFILE);

        deleteAndTruncateTable("BLOBTBL2", 1);

        commit();

        deleteAndTruncateTable("BLOBTBL", 3);
    }

    public void test_02_BlobNegative() throws SQLException {
        // Negative Test, use setBlob api to insert a 4GB blob.
        setAutoCommit(false);
        PreparedStatement insertBlob =
                prepareStatement("INSERT INTO BLOBTBL values (?,?,?,?)");

        BlobImplT _4GbBlob =
                new BlobImplT(new RandomByteStreamT(new java.util.Random(),
                        _4GB), _4GB);

        try {
            insertBlob_SetBlob("BlobTest #7 (setBlob with 4Gb blob",
                    insertBlob, _4GbBlob,
                    _4GB, 0, 1, 0);
            fail("Inserting 4BG blob should have thrown exception");
        } catch (SQLException sqle) {
            // DERBY DOES NOT SUPPORT INSERT OF 4GB BLOB
            if (usingDerbyNetClient()) {
                // DERBY-5338 client gives wrong SQLState and protocol error
                // inserting a 4GB clob. Should be 22003
                assertSQLState("XN015",sqle);
            } else {
                assertSQLState("22003", sqle);
            }
            commit();
        }
        // ADD NEW TESTS HERE
    }

    /**
     * tests using clobs
     * 
     * @throws Exception
     */
    public void test_03_Clob1() throws Exception {
        setAutoCommit(false);
        PreparedStatement insertClob =
                prepareStatement("INSERT INTO CLOBTBL values (?,?,?,?)");
        PreparedStatement selectClob =
                prepareStatement("SELECT CONTENT,DLEN FROM CLOBTBL WHERE ID = ?");

        // Test - 2Gb blob
        // Note with setCharacterStream interface the maximum size for the
        // stream has to be max value for a int which is (2GB -1 )
        // first do insert clob of 2g, 2 rows
        insertClob_SetCharacterStream("ClobTest #1", insertClob,
                BIGGEST_LOB_SZ, 0, 2, BIGGEST_LOB_SZ);
        // do a select to see if the inserts in test above went ok
        selectClob("ClobTest #2", selectClob, BIGGEST_LOB_SZ, 0, 1);
        selectClob("ClobTest #3", selectClob, BIGGEST_LOB_SZ, 0, 1);
        // do a select and then update a row of 2gb size: uses getClob
        selectUpdateClob("ClobTest #4", selectClob, BIGGEST_LOB_SZ, 0, 1);

    }

    /**
     * @throws Exception
     */
    public void test_04_Clob2() throws Exception {
        setAutoCommit(false);
        PreparedStatement selectClob =
                prepareStatement("SELECT CONTENT,DLEN FROM CLOBTBL WHERE ID = ?");
        PreparedStatement insertClob2 =
                prepareStatement("INSERT INTO CLOBTBL2 values (?,?,?,?)");
        PreparedStatement selectClob2 =
                prepareStatement("SELECT CONTENT,DLEN FROM CLOBTBL2 WHERE ID = ?");

        // Test - generate random data, write to a file, use it to insert
        // data into clob and then read back and compare if all is ok
        // currently in fvt ( derbyall), tests check for substrings etc and
        // for small amounts of data. This test will test for 100mb of clob data
        writeToFile(CHARDATAFILE, new RandomCharReaderT(new java.util.Random(),
                BIG_LOB_SZ));
        insertClob2("ClobTest #5.1 ", insertClob2, BIG_LOB_SZ, 0, 1,
                   BIG_LOB_SZ, CHARDATAFILE);
        selectClob2("ClobTest #5.2 ", selectClob2, BIG_LOB_SZ, 0, 1,
                   CHARDATAFILE);
        // DERBY-5344 updateClob2 test in LobLimitsTest gets OutOfMemoryError 
        // on updateRow with embedded
        // Disabled for embedded for the big test for now, this will materialize.
        // As part of DERBY-1903 / DERBY-5344, the test was enabled for 
        // client. That issue will have reference to the materialization bug when 
        // it is found or filed.
        if (!(usingEmbedded()  && BIGGEST_LOB_SZ  == _2GB)) {
            updateClob2("ClobTest #8.1",selectClob,BIG_LOB_SZ,0,0,10,CHARDATAFILE);
        }
        // update the 2gb row in clobtbl with the 100mb data and compare if the
        // update
        // went ok.
        selectUpdateClob2("ClobTest #8.2", selectClob2, selectClob,
                BIG_LOB_SZ, 0, 1, CHARDATAFILE);

        // test for trailing space truncation
        // insert 100mb+33k of data which has 33k of trailing space,
        // into a column of 100mb
        // insert should be successful, select should retrieve 100mb of data

        // Generate random data and write to a file, this file will be used
        // in the verification process after inserts and updates.
        writeToFile(CHARDATAFILE, new RandomCharReaderT(new java.util.Random(),
                   (NUM_TRAILING_SPACES + BIG_LOB_SZ), NUM_TRAILING_SPACES));
        insertClob2("ClobTest #6.1 ", insertClob2, BIG_LOB_SZ, 3, 1,
                   (NUM_TRAILING_SPACES + BIG_LOB_SZ), CHARDATAFILE);
        // select will retrieve data and verify the data inserted.
        selectClob2("ClobTest #6.2 ", selectClob2, BIG_LOB_SZ, 3, 1,
                   CHARDATAFILE);

    }

    public void test_05_ClobNegative() throws Exception {
        setAutoCommit(false);
        PreparedStatement insertClob =
                prepareStatement("INSERT INTO CLOBTBL values (?,?,?,?)");
        PreparedStatement selectClob =
                prepareStatement("SELECT CONTENT,DLEN FROM CLOBTBL WHERE ID = ?");
        PreparedStatement insertClob2 =
                prepareStatement("INSERT INTO CLOBTBL2 values (?,?,?,?)");
        PreparedStatement selectClob2 =
                prepareStatement("SELECT CONTENT,DLEN FROM CLOBTBL2 WHERE ID = ?");

        negativeSpaceTruncationTest("ClobTest #7");

        // Test - for stream contains a trailing non-space character
        // insert should throw an error
        writeToFile(CHARDATAFILE, new RandomCharReaderT(new java.util.Random(),
                MORE_DATA_THAN_COL_WIDTH));
        // DERBY-5341 : Client allows clob larger than
        // column width to be inserted.
        if (!usingDerbyNetClient()) {
            try {
                insertClob2("ClobTest #9.1 ", insertClob2,
                        MORE_DATA_THAN_COL_WIDTH, 4, 1,
                        MORE_DATA_THAN_COL_WIDTH, CHARDATAFILE);
                fail("ClobTest #9.1 " + "should have thrown XSDA4");
            } catch (SQLException sqle) {
                assertSQLState("XSDA4", sqle);
            }
        }
        // no row must be retrieved.
        selectClob2("ClobTest #9.2 ", selectClob2, BIG_LOB_SZ, 4, 0,
                   CHARDATAFILE);
        try {
            insertClob2("ClobTest #10 ", insertClob2,
                    MORE_DATA_THAN_COL_WIDTH, 4, 1,
                       MORE_DATA_THAN_COL_WIDTH + 1, CHARDATAFILE);
            fail("ClobTest #10. Should have thrown XSDA4");
        } catch (SQLException sqle) {
            // NEGATIVE TEST - Expected Exception: truncation of non-blanks not
            // allowed and
            // stream length is one greater than actual length of the stream
            assertSQLState("XSDA4", sqle);
        }

        try {
            insertClob2("ClobTest #11 ", insertClob2,
                    MORE_DATA_THAN_COL_WIDTH, 4, 1,
                       MORE_DATA_THAN_COL_WIDTH - 1, CHARDATAFILE);
            fail("ClobTest #11. Should have thrown XSDA4");
        } catch (SQLException sqle) {
            // NEGATIVE TEST - Expected Exception: truncation of non-blanks not
            // allowed and
            // stream length is one less than actual length of the stream
            assertSQLState("XSDA4", sqle);
        }
        deleteAndTruncateTable("CLOBTBL2", 2);

        try {
            // give -ve streamlength
            insertClob_SetCharacterStream("ClobTest #12.1", insertClob,
                    BIG_LOB_SZ, 4, 1, -1);
            fail("ClobTest #12. Should have thrown XJ025");
        } catch (SQLException sqle) {
            assertSQLState("XJ025", sqle);
        }

        selectClob("ClobTest #12.2", selectClob, BIG_LOB_SZ, 4, 0);

        deleteAndTruncateTable("CLOBTBL", 2);

        // Negative tests use the setClob API to insert a 4GB clob

        // long _4GB = 4*1024*1024*(1024L);

        ClobImplT _4GBClob =
                new ClobImplT(new RandomCharReaderT(new java.util.Random(),
                        _4GB), _4GB);

        try {
            insertClob_SetClob("ClobTest #13 (setClob with 4Gb clob",
                    insertClob, _4GBClob,
                    _4GB, 0, 1, 0);
            fail("ClobTest #13. Should have thrown 22033");
        } catch (SQLException sqle) {
         // DERBY DOES NOT SUPPORT INSERT OF 4GB CLOB
            if (usingDerbyNetClient()) {
                // DERBY-5338 client gives wrong SQLState and protocol error
                // inserting a 4GB clob. Should be 22003
                assertSQLState("XN015",sqle);
            } else {
                assertSQLState("22003", sqle);
            }
        }
        rollback();

        // ADD NEW TESTS HERE
    }

    //DERBY-5638
    // Following shutdown will ensure that all the logs are applied to the
    // database and hence there are no unapplied log files left at the end 
    // of the suite. 
    //This test deals will large data objects which can cause us to have 
    // large log files and if the database is not shutdown at the end of the 
    // suite, the suite will finish successfully but will leave a database 
    // directory with large number of big log files. Nightly machines which
    // run this suite on a regular basis can eventually run out of disk space
    // if those machines do not delete the database directories from multiple 
    // runs.
    public void test_06_shutdownDB() throws Exception {
        TestConfiguration.getCurrent().shutdownDatabase();
    }

    private void negativeSpaceTruncationTest(String msg)
            throws Exception {
        PreparedStatement insertClob2 =
                prepareStatement("INSERT INTO CLOBTBL2 values (?,?,?,?)");

        // Negative test, stream has trailing spaces but the stream length is
        // one
        // more than the actual length of the stream
        try {
            insertClob2(msg, insertClob2, BIG_LOB_SZ, 4, 1,
                    (NUM_TRAILING_SPACES + BIG_LOB_SZ - 1), CHARDATAFILE);
            fail(msg +". Should have thrown XSDA4");
        } catch (SQLException sqle) {
            // EXPECTED EXCEPTION - stream has trailing spaces,but stream
            // length is 1 less than actual length of stream
            assertSQLState("XSDA4", sqle);
        }

        try {
            insertClob2(msg, insertClob2, BIG_LOB_SZ, 5, 1,
                    (NUM_TRAILING_SPACES + BIG_LOB_SZ + 1), CHARDATAFILE);
            fail(msg + ". Should have thrown XSDA4");
        } catch (SQLException sqle) {
            // EXPECTED EXCEPTION - stream has trailing spaces,but stream
            // length is 1 greater than actual length of stream
            assertSQLState("XSDA4", sqle);
        }
    }

    /**
     * insert blob
     * 
     * @param bloblen length of blob to insert
     * @param start start id value for insert
     * @param rows insert rows number of rows
     * @param streamLength stream length passed to setBinaryStream(,,length)
     */
    private void insertBlob_SetBinaryStream(String testId, PreparedStatement ps, int bloblen, int start,
            int rows, int streamLength) throws SQLException {
        println("========================================");
        println("START " + testId + "insertBlob of size = "
                + bloblen);
        long ST = System.currentTimeMillis();

        int count = 0;
        java.util.Random random = new java.util.Random();
        for (int i = start; i < start + rows; i++) {
            ps.setInt(1, i);
            ps.setInt(2, 0);
            ps.setLong(3, bloblen);
            ps.setBinaryStream(4, new RandomByteStreamT(random, bloblen),
                    streamLength);
            count += ps.executeUpdate();
        }
        commit();
        println("Insert Blob (" + bloblen + ")" + " rows= "
                   + count + " = " + (long) (System.currentTimeMillis() - ST));
        verifyTest(count, rows, " Rows inserted with blob of size (" + bloblen
                + ") =");
        println("========================================");

    }

    /**
     * insert blob, using a setBlob api.
     * 
     * @param bloblen length of blob to insert
     * @param blob blob to insert
     * @param start start id value for insert
     * @param rows insert rows number of rows
     * @param expectedRows rows expected to be inserted
     */
    private void insertBlob_SetBlob(String testId,
            PreparedStatement ps, java.sql.Blob blob, long bloblen, int start,
            int rows, int expectedRows) throws SQLException {
        println("========================================");
        println("START " + testId + "insertBlob of size = "
                + bloblen);
        long ST = System.currentTimeMillis();
        int count = 0;
        for (int i = start; i < start + rows; i++) {
            ps.setInt(1, i);
            ps.setInt(2, 0);
            ps.setLong(3, bloblen);
            ps.setBlob(4, blob);
            count += ps.executeUpdate();
        }
        commit();
        println("Insert Blob (" + bloblen + ")" + " rows= "
                        + count + " = "
                        + (long) (System.currentTimeMillis() - ST));

        verifyTest(count, expectedRows,
                " Rows inserted with blob of size (" + bloblen + ") =");
        println("========================================");

    }

    /**
     * select from blob table (BLOBTBL)
     * 
     * @param bloblen select expects to retrieve a blob of this length
     * @param id id of the row to retrieve
     * @param expectedRows number of rows expected to match id
     */
    private void selectBlob(String testId,
            PreparedStatement ps, int bloblen, int id, int expectedRows)
            throws SQLException {
        println("========================================");
        println("START " + testId + " - SELECT BLOB of size = "
                + bloblen);

        long ST = System.currentTimeMillis();

        int count = 0;
        ps.setInt(1, id);
        ResultSet rs = ps.executeQuery();

        while (rs.next()) {
            count++;
            Blob value = rs.getBlob(1);
            long l = value.length();
            long dlen = rs.getLong(2);
            assertEquals("FAIL - MISMATCH LENGTHS GOT " + l
                       + " expected " + dlen + " for row in BLOBTBL with ID="
                       + id, dlen, l);
        }

        rs.close();
        commit();

        verifyTest(count, expectedRows,
                "Matched rows selected with blob of size(" + bloblen + ") =");
        println("Select Blob (" + bloblen + ")" + " rows= "
                   + expectedRows + " = "
                   + (long) (System.currentTimeMillis() - ST));
        println("========================================");
    }

    /**
     * insert blob into BLOBTBL2
     * 
     * @param bloblen length of blob to insert
     * @param start id value for insert
     * @param rows insert rows number of rows
     * @param streamLength stream length passed to setBinaryStream(,,length)
     * @param file filename to match retrieved data against
     */

    private void insertBlob2(String testId,
            PreparedStatement ps, int bloblen, int start, int rows,
            int streamLength, String file) throws Exception {
        println("========================================");
        println("START " + testId + "insert Blob of size = "
                + bloblen);
        int count = 0;
        long ST = System.currentTimeMillis();

        for (int i = start; i < start + rows; i++) {
            FileInputStream fis =
                PrivilegedFileOpsForTests.getFileInputStream(new File(file));
            ps.setInt(1, i);
            ps.setInt(2, 0);
            ps.setLong(4, bloblen);
            ps.setBinaryStream(3, fis, streamLength);
            count += ps.executeUpdate();
            fis.close();
        }
        commit();
        println("Insert Blob (" + bloblen + ")" + " rows= "
                   + count + " = " + (long) (System.currentTimeMillis() - ST));
        verifyTest(count, rows, " Rows inserted with blob of size (" + bloblen
                + ") =");
        println("========================================");

    }

    /**
     * select from blob table (BLOBTBL2)
     * 
     * @param bloblen select expects to retrieve a blob of this length
     * @param id id of the row to retrieve
     * @param expectedRows number of rows expected to match id
     * @param file name of the file,against which the retrieved data is compared
     */
    private void selectBlob2(String testId,
            PreparedStatement ps, int bloblen, int id, int expectedRows,
            String file) throws Exception {
        println("========================================");
        println("START " + testId + " - SELECT BLOB of size = "
                + bloblen);

        long ST = System.currentTimeMillis();

        int count = 0;
        ps.setInt(1, id);
        ResultSet rs = ps.executeQuery();

        while (rs.next()) {
            count++;
            Blob value = rs.getBlob(1);
            long l = value.length();
            long dlen = rs.getLong(2);
            assertEquals("FAIL - MISMATCH LENGTHS GOT " + l
                       + " expected " + dlen + " for row in BLOBTBL with ID="
                       + id, dlen, l);

            compareBlobToFile(value.getBinaryStream(), file);
        }

        rs.close();
        commit();

        verifyTest(count, expectedRows,
                "Matched rows selected with blob of size(" + bloblen + ") =");

        println("Select Blob (" + bloblen + ")" + " rows= "
                   + expectedRows + " = "
                   + (long) (System.currentTimeMillis() - ST));
        println("========================================");
    }

    /**
     * Basically this test will do an update using setBlob api - select row from
     * blobtbl and then update a row in blobtbl and verify updated data in
     * blobtbl
     * 
     * @param ps select statement from which blob is retrieved
     * @param bloblen updating value is of length bloblen
     * @param id id of the row retrieved, for the update
     * @param updateId id of the row that is updated
     */
    private void selectUpdateBlob(String testId,
            PreparedStatement ps, int bloblen, int id, int updateId)
            throws Exception {
        println("========================================");
        println("START " + testId + " - select and then update blob of size= "
                + bloblen + " - Uses getBlob api");

        ps.setInt(1, id);
        ResultSet rs = ps.executeQuery();
        rs.next();
        Blob value = rs.getBlob(1);
        long l = value.length();
        long dlen = rs.getLong(2);
        assertEquals("FAIL - MISMATCH LENGTHS GOT " + l + " expected "
                           + dlen + " for row in BLOBTBL with ID=" + id,
                           dlen, l);

        PreparedStatement psUpd =
                prepareStatement("update BLOBTBL set content=?,dlen =?" +
                        "where id = ?");
        psUpd.setBlob(1, value);
        psUpd.setLong(2, l);
        psUpd.setInt(3, updateId);

        println("Rows Updated = " + psUpd.executeUpdate());
        commit();

        // now select and verify that update went through ok.
        ps.setInt(1, updateId);
        ResultSet rs2 = ps.executeQuery();
        rs2.next();
        Blob updatedValue = rs2.getBlob(1);
        assertEquals(
                "FAIL - Retrieving the updated blob length does not match " +
                        "expected length = " + l + " found = "
                        + updatedValue.length(),
                   l, updatedValue.length());

        commit();

        // close resultsets
        rs.close();
        rs2.close();
        println("========================================");
    }

    /**
     * Basically this test will do an insert using setBlob api - select row from
     * blobtbl and then insert a row in blobtbl and verify updated data in
     * blobtbl
     * 
     * @param ps select statement from which blob is retrieved
     * @param bloblen updating value is of length bloblen
     * @param id id of the row retrieved, for the update
     * @param insertId id of the row that is inserted
     */
    private void selectInsertBlob(String testId,
            PreparedStatement ps, PreparedStatement ins, int bloblen, int id,
            int insertId) throws Exception {
        println("========================================");
        println("START " + testId + " - select and then insert blob of size= "
                + bloblen
                + " - Uses getBlob api to do select and setBlob for insert");

        ps.setInt(1, id);
        ResultSet rs = ps.executeQuery();
        rs.next();
        Blob value = rs.getBlob(1);
        long l = value.length();
        long dlen = rs.getLong(2);
        assertEquals("FAIL - MISMATCH LENGTHS GOT " + l + " expected "
                           + dlen + " for row in BLOBTBL with ID=" + id,
                           dlen, l);
        ins.setInt(1, insertId);
        ins.setInt(2, 0);
        ins.setLong(3, l);
        ins.setBlob(4, value);

        // assert one row updated
        assertUpdateCount(ins, 1);
        commit();

        // now select and verify that update went through ok.
        ps.setInt(1, insertId);
        ResultSet rs2 = ps.executeQuery();
        rs2.next();
        Blob insertedValue = rs2.getBlob(1);
        assertEquals(
                "FAIL - Retrieving the updated blob length does not match " +
                        "expected length = " + l + " found = "
                        + insertedValue.length(),
                l, insertedValue.length());

        commit();

        // close resultsets
        rs.close();
        rs2.close();
        println("========================================");
    }

    /**
     * Basically this test will do an update using setBinaryStream api and
     * verifies the updated data. select row from blobtbl2 and then update a row
     * in blobtbl and verify updated data in blobtbl
     * 
     * @param bloblen updating value is of length bloblen
     * @param id id of the row retrieved, for the update
     * @param updateId id of the row that is updated
     * @param file name of the file,against which the updated data is compared
     */
    private void selectUpdateBlob2(String testId,
            PreparedStatement ps, PreparedStatement sel, int bloblen, int id,
            int updateId, String file) throws Exception {
        println("========================================");
        println("START " + testId + " - select and then update blob of size= "
                + bloblen + " - Uses getBlob and setBlob  api");

        // retrieve row from blobtbl2
        ps.setInt(1, id);
        ResultSet rs = ps.executeQuery();
        rs.next();
        Blob value = rs.getBlob(1);
        long l = value.length();
        long dlen = rs.getLong(2);
        assertEquals("FAIL - MISMATCH LENGTHS GOT " + l + " expected "
                + dlen + " for row in BLOBTBL2 with ID=" + id, dlen, l);

        PreparedStatement psUpd =
                prepareStatement("update BLOBTBL set content=?,dlen =?" +
                        " where id = ?");
        psUpd.setBlob(1, value);
        psUpd.setLong(2, l);
        psUpd.setInt(3, updateId);
        // assert 1 row updated
        assertUpdateCount(psUpd, 1);
        commit();

        // now select and verify that update went through ok.
        sel.setInt(1, updateId);
        ResultSet rs2 = sel.executeQuery();
        rs2.next();
        Blob updatedValue = rs2.getBlob(1);
        assertEquals("FAIL - MISMATCH length of updated blob value : expected="
                +
                l + " found =" + updatedValue.length(), l, updatedValue
                .length());
        compareBlobToFile(updatedValue.getBinaryStream(), file);

        commit();

        // close resultsets
        rs.close();
        rs2.close();
        println("========================================");

    }

    private static void compareBlobToFile(InputStream lobstream, String filename)
            throws Exception {
        FileInputStream file =
                PrivilegedFileOpsForTests
                        .getFileInputStream(new File(filename));
        assertEquals(file, lobstream);
    }

    private void deleteAndTruncateTable(String table,
            int expectedRows) throws SQLException {
    	Statement s = createStatement();
        //Keep the delete call to exercise delete of long blobs and clobs.
    	// This is a separate code path through Derby compared to truncate
    	// table code path.
        int count = s.executeUpdate(
                "DELETE FROM " + JDBC.escape(table));
        //DERBY-5638
        //Adding truncate call which will give back the disk space being 
        // used by the table at commit time rather than wait for test 
        // infrastructure to drop the table.
        s.executeUpdate("TRUNCATE TABLE " + JDBC.escape(table));
        commit();
        verifyTest(count, expectedRows, "Rows deleted =");
    }

    /**
     * insert clob
     * 
     * @param cloblen length of clob to insert
     * @param start id value for insert
     * @param rows insert rows number of rows
     * @param streamLength stream length passed to
     *            setCharacterStream(...,length)
     */
    private void insertClob_SetCharacterStream(String testId, PreparedStatement ps, int cloblen, int start,
            int rows, int streamLength) throws SQLException {
        println("========================================");
        println("START " + testId + "  -insertClob of size = "
                + cloblen);

        java.util.Random random = new java.util.Random();
        int count = 0;
        long ST = System.currentTimeMillis();

        for (int i = start; i < start + rows; i++) {
            ps.setInt(1, i);
            ps.setInt(2, 0);
            ps.setLong(3, cloblen);
            ps.setCharacterStream(4, new RandomCharReaderT(random, cloblen),
                    streamLength);
            count += ps.executeUpdate();
        }
        commit();
        println("Insert Clob (" + cloblen + ")" + " rows= "
                   + count + " = " + (long) (System.currentTimeMillis() - ST));
        verifyTest(count, rows, "Rows inserted with clob of size (" + cloblen
                + ") = ");
        println("========================================");

    }

    /**
     * insert clob, using a setClob api.
     * 
     * @param cloblen length of clob to insert
     * @param clob clob to insert
     * @param start start id value for insert
     * @param rows insert rows number of rows
     * @param expectedRows rows expected to be inserted
     */
    private void insertClob_SetClob(String testId,
            PreparedStatement ps, java.sql.Clob clob, long cloblen, int start,
            int rows, int expectedRows) throws SQLException {
        println("========================================");
        println("START " + testId + "insertClob of size = " + cloblen);
        long ST = System.currentTimeMillis();
        int count = 0;

        for (int i = start; i < start + rows; i++) {
            ps.setInt(1, i);
            ps.setInt(2, 0);
            ps.setLong(3, cloblen);
            ps.setClob(4, clob);
            count += ps.executeUpdate();
        }
        commit();
        println("Insert Clob (" + cloblen + ")" + " rows= " + count + " = "
                + (long) (System.currentTimeMillis() - ST));

        verifyTest(count, expectedRows, " Rows inserted with clob of size ("
                + cloblen + ") =");
        println("========================================");

    }

    /**
     * select from clob table
     * 
     * @param cloblen select expects to retrieve a clob of this length
     * @param id id of the row to retrieve
     * @param expectedRows number of rows expected to match id
     */
    private void selectClob(String testId,
            PreparedStatement ps, int cloblen, int id, int expectedRows)
            throws SQLException {
        println("========================================");
        println("START " + testId + " - SELECT CLOB of size = "
                + cloblen);

        int count = 0;
        long ST = System.currentTimeMillis();

        ps.setInt(1, id);
        ResultSet rs = ps.executeQuery();
        while (rs.next()) {
            count++;
            Clob value = rs.getClob(1);
            long l = value.length();
            long dlen = rs.getLong(2);
            assertEquals("FAIL - MISMATCH LENGTHS GOT " + l
                    + " expected " + dlen + " for row in CLOBTBL with ID="
                    + id, l, dlen);
        }
        rs.close();
        commit();

        println("Select Clob (" + cloblen + ")" + " rows= "
                + expectedRows + " = "
                + (long) (System.currentTimeMillis() - ST));

        verifyTest(count, expectedRows,
                "Matched rows selected with clob of size(" + cloblen + ") =");
        println("========================================");

    }

    /**
     * insert clob into CLOBTBL2
     * 
     * @param cloblen length of clob to insert
     * @param start id value for insert
     * @param rows insert rows number of rows
     * @param streamLength stream length passed to
     *            setCharacterStream(pos,reader,streamLength)
     * @param file name of the file that has data to be inserted
     */
    private void insertClob2(String testId,
            PreparedStatement ps, int cloblen, int start, int rows,
            int streamLength, String file) throws Exception {
        println("========================================");
        println("START " + testId + "insert Clob of size = "
                + cloblen);
        int count = 0;
        long ST = System.currentTimeMillis();
        for (int i = start; i < start + rows; i++) {
            FileReader reader = PrivilegedFileOpsForTests
                        .getFileReader(new File(file));
            try {
                println("Got reader for file " + file + " " + reader);
                ps.setInt(1, i);
                ps.setInt(2, 0);
                ps.setLong(4, cloblen);
                ps.setCharacterStream(3, reader, streamLength);
                count += ps.executeUpdate();
            } finally {
                reader.close();
                println("Closed reader for file " + file + " " + reader);
            }
        }
        commit();

        println("Insert Clob (" + cloblen + ")" + " rows= "
                   + count + " = " + (long) (System.currentTimeMillis() - ST));
        verifyTest(count, rows, " Rows inserted with clob of size (" + cloblen
                + ") =");
        println("========================================");

    }

    /**
     * select from clob table (CLOBTBL2)
     * 
     * @param cloblen select expects to retrieve a clob of this length
     * @param id id of the row to retrieve
     * @param expectedRows number of rows expected to match id
     * @param file filename to compare the retrieved data against
     */
    private void selectClob2(String testId,
            PreparedStatement ps, int cloblen, int id, int expectedRows,
            String file) throws Exception {
        println("========================================");
        println("START " + testId + " - SELECT CLOB of size = "
                + cloblen);

        long ST = System.currentTimeMillis();

        int count = 0;
        ps.setInt(1, id);
        ResultSet rs = ps.executeQuery();

        while (rs.next()) {
            count++;
            Clob value = rs.getClob(1);
            long l = value.length();
            long dlen = rs.getLong(2);
            assertEquals("FAIL - MISMATCH LENGTHS GOT " + l
                    + " expected " + dlen + " for row in CLOBTBL2 with ID="
                    + id, l, cloblen);
            compareClobToFile(value.getCharacterStream(), file, cloblen);
        }

        rs.close();
        commit();

        verifyTest(count, expectedRows,
                "Matched rows selected with clob of size(" + cloblen + ") =");

        println("Select Clob (" + cloblen + ")" + " rows= "
                   + expectedRows + " = "
                   + (long) (System.currentTimeMillis() - ST));
        println("========================================");
    }

    /*
     * Basically this test will do an update using setClob api - select row from
     * clobtbl and then update a row in clobtbl and verify updated data in
     * clobtbl
     */
    private void selectUpdateClob(String testId,
            PreparedStatement ps, int cloblen, int id, int updateId) throws Exception {
        println("========================================");
        println("START " + testId + " - select and then update clob of size= "
                + cloblen + " - Uses setClob api");

        ps.setInt(1, id);
        ResultSet rs = ps.executeQuery();
        rs.next();
        Clob value = rs.getClob(1);
        long l = value.length();
        long dlen = rs.getLong(2);
        assertEquals("FAIL - MISMATCH LENGTHS GOT " + l + " expected "
                + dlen + " for row in CLOBTBL with ID=" + id, dlen, l);
                
        PreparedStatement psUpd =
                prepareStatement("update CLOBTBL set content=?, "
                + "dlen =? where id = ?");
        psUpd.setCharacterStream(1, value.getCharacterStream(), (int) l);
        psUpd.setLong(2, l);
        psUpd.setInt(3, updateId);
        if (usingDerbyNetClient()) {
            // DERBY-5317 cannot use setCharacterStream with value from
            // Clob.getCharacterStream because server will try to stream
            // lob to and from server at the same time. setClob can be
            // used as a work around.
            // Verify that new error is thrown 
            assertPreparedStatementError("XN023", psUpd);
            return;
        } else {
            assertUpdateCount(psUpd, 1);
        }
        commit();

        // now select and verify that update went through ok.
        ps.setInt(1, updateId);
        ResultSet rs2 = ps.executeQuery();
        rs2.next();
        Clob updatedValue = rs2.getClob(1);
        assertEquals(
                "FAIL - Retrieving the updated clob length does not match " +
                        "expected length = " + l + " found = "
                        + updatedValue.length(), l,
                updatedValue.length());

        commit();

        // close resultsets
        rs.close();
        rs2.close();
        println("========================================");
    }

    /*
     * Basically this test will do an update using setBlob api and verifies the
     * updated data. select row from clobtbl2 and then update a row in clobtbl
     * and verify updated data in clobtbl against the data in the original file
     */
    private void selectUpdateClob2(String testId,
            PreparedStatement ps, PreparedStatement sel, int cloblen, int id,
            int updateId, String file) throws Exception {
        println("========================================");
        println("START " + testId + " - select and then update clob of size= "
                + cloblen + " - Uses setClob api");

        // retrieve row from clobtbl2
        ps.setInt(1, id);
        ResultSet rs = ps.executeQuery();
        rs.next();
        Clob value = rs.getClob(1);
        long l = value.length();
        long dlen = rs.getLong(2);
        assertEquals("FAIL - MISMATCH LENGTHS GOT " + l + " expected "
                + dlen + " for row in CLOBTBL2 with ID=" + id, dlen, l);

        PreparedStatement psUpd =
               prepareStatement("update CLOBTBL set content=?,dlen =? " +
                                "where id = ?");
        psUpd.setClob(1, value);
        psUpd.setLong(2, l);
        psUpd.setInt(3, updateId);

        assertUpdateCount(psUpd, 1);
        commit();

        // now select and verify that update went through ok.
        sel.setInt(1, updateId);
        ResultSet rs2 = sel.executeQuery();
        rs2.next();
        Clob updatedValue = rs2.getClob(1);
        assertEquals("FAIL - MISMATCH length of updated clob value , found=" +
                   updatedValue.length() + ",expected = " + l, l, updatedValue
                .length());
        compareClobToFile(updatedValue.getCharacterStream(), file, (int) l);
        commit();

        // close resultsets
        rs.close();
        rs2.close();
        println("========================================");

    }

    /*
     * Basically this test will do an update using updateClob api and verifies
     * the updated data. select row from clobtbl2 and then update a row in
     * clobtbl and verify updated data in clobtbl against the data in the
     * original file
     * 
     * @param updateRowId id of the row that needs to be updated
     */
    private void updateClob2(String testId,
            PreparedStatement sel,
            int cloblen, int id, int updateRowId, int updateIdVal, String file)
            throws Exception {
        println("========================================");
        println("START " + testId
                + " - select and then update clob of size= "
                + cloblen + " - Uses updateClob api");

        PreparedStatement ps1 =
                        prepareStatement("SELECT * FROM CLOBTBL FOR UPDATE",
                                ResultSet.TYPE_FORWARD_ONLY,
                                ResultSet.CONCUR_UPDATABLE);
        PreparedStatement ps =
                prepareStatement("SELECT CONTENT,DLEN FROM CLOBTBL2 " +
                        "where ID =?");

        ps.setInt(1, id);
        // retrieve row from clobtbl2
        ResultSet rs = ps.executeQuery();
        rs.next();
        Clob value = rs.getClob(1);
        long l = value.length();
        long dlen = rs.getLong(2);
        if (dlen != l) {
            println("FAIL - MISMATCH LENGTHS GOT " + l + " expected "
                            + dlen + " for row in CLOBTBL2 with ID=" + id);
        }

        ResultSet rs1 = ps1.executeQuery();
        while (rs1.next()) {
            if (rs1.getInt(1) == updateRowId) {
                rs1.updateClob(4, value);
                rs1.updateInt(1, updateIdVal);
                rs1.updateInt(2, 0);
                rs1.updateLong(3, dlen);
                rs1.updateRow();
                break;
            }
        }

        commit();

        // close resultsets
        rs.close();
        rs1.close();

        // verify
        // now select and verify that update went through ok.
        sel.setInt(1, updateIdVal);
        ResultSet rs2 = sel.executeQuery();
        rs2.next();
        Clob updatedValue = rs2.getClob(1);
        assertEquals("FAIL - MISMATCH length of updated clob value ," +
                "found=" + 
                updatedValue.length() + ",expected = " + l,
                l, updatedValue.length());
        compareClobToFile(updatedValue.getCharacterStream(), file, (int) l);

        if (updatedValue.length() != l) {
            println("FAIL - MISMATCH length of updated clob value ," +
                            "found="
                            +
                            updatedValue.length() + ",expected = " + l);
        } else
            compareClobToFile(updatedValue.getCharacterStream(), file, (int) l);

        println("========================================");

    }

    private static void compareClobToFile(Reader lobstream, String filename,
            int length)
            throws Exception {
        FileReader file = PrivilegedFileOpsForTests.getFileReader(
                   new File(filename));
        int c1 = 0;
        int c2 = 0;
        long count = 0;
        do {
            c1 = lobstream.read();
            c2 = file.read();
            assertEquals(
                    "FAIL -- MISMATCH in data stored versus data retrieved at "
                            +
                            count + " " + c1 + " does not match " + c2,
                    c2, c1);
            count++;
            length--;
        } while (c1 != -1 && c2 != -1 && length > 0);
        file.close();
    }

    private static void verifyTest(int affectedRows, int expectedRows,
            String test) {
        assertEquals("FAIL --" + test + affectedRows
                + " , but expected rows =" + expectedRows, expectedRows,
                affectedRows);
        println(test + affectedRows);
    }

    private static void writeToFile(String file, Reader r)
            throws IOException {
        // does file exist, if so delete and write to a fresh file
        File f = new File(file);
        if (PrivilegedFileOpsForTests.exists(f)) {
            assertTrue(PrivilegedFileOpsForTests.delete(f));
        }
        FileWriter writer = PrivilegedFileOpsForTests.getFileWriter(f);
        println("Got FileWriter for " + file + " " + writer);
        // write in chunks of 32k buffer
        char[] buffer = new char[32 * 1024];
        int count = 0;

        while ((count = r.read(buffer)) >= 0)
            writer.write(buffer, 0, count);
        writer.flush();
        writer.close();
        println("writer " + writer + " for file " + file + " closed");
    }
}

/**
 * Class to generate random byte data
 */
class RandomByteStreamT extends java.io.InputStream {
    private long length;

    private java.util.Random dpr;

    RandomByteStreamT(java.util.Random dpr, long length) {
        this.length = length;
        this.dpr = dpr;

    }

    public int read() {
        if (length <= 0)
            return -1;

        length--;
        return (byte) (dpr.nextInt() >>> 25);
    }

    public int read(byte[] data, int off, int len) {

        if (length <= 0)
            return -1;

        if (len > length)
            len = (int) length;

        for (int i = 0; i < len; i++) {
            // chop off bits and return a +ve byte value.
            data[off + i] = (byte) (dpr.nextInt() >>> 25);
        }

        length -= len;
        return len;
    }
}

/*
 * Class to generate random char data, generates 1,2,3bytes character.
 */
class RandomCharReaderT extends java.io.Reader {
    private long length;
    private long numTrailingSpaces;

    private java.util.Random dpr;

    RandomCharReaderT(java.util.Random dpr, long length) {
        this.length = length;
        this.dpr = dpr;
        this.numTrailingSpaces = 0;
    }

    RandomCharReaderT(java.util.Random dpr, long length, long numTrailingSpaces) {
        this.length = length;
        this.dpr = dpr;
        this.numTrailingSpaces = numTrailingSpaces;
    }

    private int randomInt(int min, int max) {
        return dpr.nextInt(max - min) + min;
    }

    private char getChar() {
        // return space for trailing spaces.
        if (length <= numTrailingSpaces) {
            return ' ';
        }

        double drand = dpr.nextDouble();
        char c = 'a';
        if (drand < 0.25)
            c = (char) randomInt((int) 'A', (int) 'Z');
        else if (drand < 0.5)
            switch (randomInt(1, 10)) {
            case 1:
                c = '\u00c0';
                break;
            case 2:
                c = '\u00c1';
                break;
            case 3:
                c = '\u00c2';
                break;
            case 4:
                c = '\u00ca';
                break;
            case 5:
                c = '\u00cb';
                break;
            case 6:
                c = '\u00d4';
                break;
            case 7:
                c = '\u00d8';
                break;
            case 8:
                c = '\u00d1';
                break;
            case 9:
                c = '\u00cd';
                break;
            default:
                c = '\u00dc';
                break;
            }
        else if (drand < 0.75)
            c = (char) randomInt((int) 'a', (int) 'z');
        else if (drand < 1.0)
            switch (randomInt(1, 10)) {
            case 1:
                c = '\u00e2';
                break;
            case 2:
                c = '\u00e4';
                break;
            case 3:
                c = '\u00e7';
                break;
            case 4:
                c = '\u00e8';
                break;
            case 5:
                c = '\u00ec';
                break;
            case 6:
                c = '\u00ef';
                break;
            case 7:
                c = '\u00f6';
                break;
            case 8:
                c = '\u00f9';
                break;
            case 9:
                c = '\u00fc';
                break;
            default:
                c = '\u00e5';
                break;
            }

        return c;

    }

    public int read() {
        if (length <= 0)
            return -1;

        length--;
        return getChar();
    }

    public int read(char[] data, int off, int len) {

        if (length <= 0)
            return -1;

        if (len > length)
            len = (int) length;

        for (int i = 0; i < len; i++) {
            data[off + i] = getChar();
            length -= 1;
        }

        return len;
    }

    public void close() {

    }
}

/**
 * Class used to simulate a 4GB Clob implementation to check whether derby
 * implements such large Clobs correctly. Derby throws an error if the clob size
 * exceeds 2GB
 **/

class ClobImplT implements java.sql.Clob {
    long length;
    Reader myReader;

    public ClobImplT(Reader myReader, long length) {
        this.length = length;
        this.myReader = myReader;
    }

    public long length() throws SQLException {
        return length;
    }

    public String getSubString(long pos, int length) throws SQLException {
        throw new SQLException("Not implemented");
    }

    public java.io.Reader getCharacterStream() throws SQLException {
        return myReader;
    }

    public java.io.InputStream getAsciiStream() throws SQLException {
        throw new SQLException("Not implemented");
    }

    public long position(String searchstr, long start) throws SQLException {
        throw new SQLException("Not implemented");
    }

    public long position(Clob searchstr, long start) throws SQLException {
        throw new SQLException("Not implemented");
    }

    public int setString(long pos, String str) throws SQLException {
        throw new SQLException("Not implemented");
    }

    public int setString(long pos, String str, int offset, int len)
            throws SQLException {
        throw new SQLException("Not implemented");
    }

    public java.io.OutputStream setAsciiStream(long pos) throws SQLException {
        throw new SQLException("Not implemented");
    }

    public java.io.Writer setCharacterStream(long pos) throws SQLException {
        throw new SQLException("Not implemented");
    }

    public void truncate(long len) throws SQLException {
        throw new SQLException("Not implemented");
    }

    public void free() throws SQLException {
        throw new SQLException("Not implemented");
    }

    public Reader getCharacterStream(long pos, long length) throws SQLException {
        throw new SQLException("Not implemented");
    }

}

/***
 * Class to simulate a 4Gb blob impl in order to test if Derby handles such
 * large blobs correctly. The main methods here are only the length() and the
 * getBinaryStream(). Rest are just placeholders/dummy methods in order to
 * implement the java.sql.Blob interface ---- Derby throws an error if the blob
 * length exceeds the max range of int.
 */
class BlobImplT implements java.sql.Blob {
    long length;
    InputStream myStream;

    public BlobImplT(InputStream is, long length) {
        this.myStream = is;
        this.length = length;
    }

    public InputStream getBinaryStream()
            throws SQLException {
        return myStream;
    }

    public byte[] getBytes()
            throws SQLException {
        throw new SQLException("Not implemented");
    }

    public long length()
            throws SQLException {
        return length;
    }

    public long position(Blob pattern, long start)
            throws SQLException {
        throw new SQLException("Not implemented");
    }

    public long position(byte[] pattern, long start)
            throws SQLException {
        throw new SQLException("Not implemented");
    }

    public OutputStream setBinaryStream(long pos)
            throws SQLException

    {
        throw new SQLException("Not implemented");
    }

    public int setBytes(long pos, byte[] bytes)
            throws SQLException {
        throw new SQLException("Not implemented");
    }

    public int setBytes(long pos, byte[] bytes, int offset, int len)
            throws SQLException {
        throw new SQLException("Not implemented");
    }

    public void truncate(long len)
            throws SQLException {
        throw new SQLException("Not implemented");
    }

    public byte[] getBytes(long pos, int length)
            throws SQLException {
        throw new SQLException("Not implemented");
    }

    public void free() throws SQLException {
        throw new SQLException("Not implemented");
    }

    public InputStream getBinaryStream(long pos, long length) throws SQLException {
        throw new SQLException("Not implemented");
    }

}
