/**
* Derby - Class org.apache.derbyTesting.functionTests.tests.memory.BlobMemTest
*
* 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.memory;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.PreparedStatement;
import java.util.Arrays;
import java.util.Properties;
import junit.framework.Test;
import junit.framework.TestSuite;
import org.apache.derbyTesting.functionTests.harness.JavaVersionHolder;
import org.apache.derbyTesting.functionTests.tests.lang.SimpleTest;
import org.apache.derbyTesting.functionTests.util.streams.LoopingAlphabetStream;
import org.apache.derbyTesting.functionTests.util.streams.LoopingAlphabetReader;
import org.apache.derbyTesting.junit.BaseJDBCTestCase;
import org.apache.derbyTesting.junit.DatabasePropertyTestSetup;
import org.apache.derbyTesting.junit.JDBC;
import org.apache.derbyTesting.junit.SystemPropertyTestSetup;
import org.apache.derbyTesting.junit.TestConfiguration;
public class BlobMemTest extends BaseJDBCTestCase {
private static final int LONG_BLOB_LENGTH = 18000000;
private static final String LONG_BLOB_LENGTH_STRING= "18000000";
private static final byte[] SHORT_BLOB_BYTES = new byte[] {0x01,0x02,0x03};
public BlobMemTest(String name) {
super(name);
}
/**
* Insert a blob and test length.
*
* @param lengthless if true use the lengthless setBinaryStream api
*
* @throws SQLException
* @throws IOException
* @throws InvocationTargetException
* @throws IllegalAccessException
* @throws IllegalArgumentException
*/
private void testBlobLength(boolean lengthless, int extraLen) throws SQLException, IOException, IllegalArgumentException, IllegalAccessException, InvocationTargetException {
setAutoCommit(false);
Statement s = createStatement();
s.executeUpdate("CREATE TABLE BLOBTAB (K INT CONSTRAINT PK PRIMARY KEY, B BLOB(" + LONG_BLOB_LENGTH + "))");
PreparedStatement ps = prepareStatement("INSERT INTO BLOBTAB VALUES(?,?)");
// We allocate 16MB for the test so use something bigger than that.
ps.setInt(1,1);
int blobLen = LONG_BLOB_LENGTH + extraLen;
LoopingAlphabetStream stream = new LoopingAlphabetStream(blobLen);
if (lengthless) {
Method m = null;
try {
Class c = ps.getClass();
m = c.getMethod("setBinaryStream",new Class[] {Integer.TYPE,
InputStream.class});
} catch (NoSuchMethodException e) {
// ignore method not found as method may not be present for
// jdk's lower than 1.6.
println("Skipping lengthless insert because method is not available");
return;
}
m.invoke(ps, new Object[] {new Integer(2),stream});
}
else
ps.setBinaryStream(2, stream,blobLen);
if (extraLen == 0)
{
ps.executeUpdate();
}
else
{
try
{
ps.executeUpdate();
fail("Expected truncation error for blob too large");
}
catch (SQLException sqlE)
{
assertSQLState("Wrong SQL State for truncation", "22001", sqlE);
}
// extraLen > 0 is just a way to force the truncation error. Once
// we've forced that error, we're done testing, so return.
return;
}
// insert a zero length blob.
ps.setInt(1, 2);
ps.setBytes(2, new byte[] {});
ps.executeUpdate();
// insert a null blob.
ps.setInt(1, 3);
ps.setBytes(2,null);
ps.executeUpdate();
// insert a short blob
ps.setInt(1, 4);
ps.setBytes(2, SHORT_BLOB_BYTES);
ps.executeUpdate();
// Currently need to use optimizer override to force use of the index.
// Derby should use sort avoidance and do it automatically, but there
// appears to be a bug.
ResultSet rs = s.executeQuery("SELECT K, LENGTH(B), B FROM BLOBTAB" +
"-- DERBY-PROPERTIES constraint=pk\n ORDER BY K");
rs.next();
assertEquals(LONG_BLOB_LENGTH_STRING,rs.getString(2));
// make sure we can still access the blob after getting length.
// It should be ok because we reset the stream
InputStream rsstream = rs.getBinaryStream(3);
int len= 0;
byte[] buf = new byte[32672];
for (;;) {
int size = rsstream.read(buf);
if (size == -1)
break;
len += size;
int expectedValue = ((len -1) % 26) + 'a';
if (size != 0)
assertEquals(expectedValue,buf[size -1]);
}
assertEquals(LONG_BLOB_LENGTH,len);
// empty blob
rs.next();
assertEquals("0",rs.getString(2));
byte[] bytes = rs.getBytes(3);
assertEquals(0, bytes.length);
// null blob
rs.next();
assertEquals(null,rs.getString(2));
bytes = rs.getBytes(3);
assertEquals(null,bytes);
// short blob
rs.next();
assertEquals("3",rs.getString(2));
bytes = rs.getBytes(3);
assertTrue(Arrays.equals(SHORT_BLOB_BYTES, bytes));
rs.close();
// Select just length without selecting the blob.
rs = s.executeQuery("SELECT K, LENGTH(B) FROM BLOBTAB " +
"ORDER BY K");
JDBC.assertFullResultSet(rs, new String [][] {{"1",LONG_BLOB_LENGTH_STRING},{"2","0"},
{"3",null},{"4","3"}});
}
/**
* Test the length after inserting with the setBinaryStream api
* that takes length. In this case the length will be encoded at the
* begining of the stream and the call should be fairly low overhead.
*
* @throws SQLException
* @throws IOException
* @throws InvocationTargetException
* @throws IllegalAccessException
* @throws IllegalArgumentException
*/
public void testBlobLength() throws SQLException, IOException, IllegalArgumentException, IllegalAccessException, InvocationTargetException {
testBlobLength(false, 0);
}
/**
* Test the length after inserting the blob value with the lengthless
* setBinaryStream api. In this case we will have to read the whole
* stream to get the length.
*
* @throws SQLException
* @throws IOException
* @throws InvocationTargetException
* @throws IllegalAccessException
* @throws IllegalArgumentException
*/
public void testBlobLengthWithLengthlessInsert() throws SQLException, IOException, IllegalArgumentException, IllegalAccessException, InvocationTargetException {
testBlobLength(true, 0);
}
/**
* Simple test to excercise message 22001 as described in DERBY-961.
*/
public void testBlobLengthTooLongDerby961() throws SQLException, IOException, IllegalArgumentException, IllegalAccessException, InvocationTargetException {
testBlobLength(false, 10000);
}
public static Test suite() {
Test suite = TestConfiguration.defaultSuite(BlobMemTest.class);
Properties p = new Properties();
// use small pageCacheSize so we don't run out of memory on the insert.
p.setProperty("derby.storage.pageCacheSize", "100");
return new SystemPropertyTestSetup(suite,p);
}
/**
* Tests that a blob can be safely occur multiple times in a SQL select and
* test that large objects streams are not being materialized when cloned.
* <p/>
* See DERBY-4477.
* @see org.apache.derbyTesting.functionTests.tests.jdbcapi.BLOBTest#testDerby4477_3645_3646_Repro
* @see ClobMemTest#testDerby4477_3645_3646_Repro_lowmem_clob
*/
public void testDerby4477_3645_3646_Repro_lowmem()
throws SQLException, IOException {
setAutoCommit(false);
Statement s = createStatement();
int blobsize = LONG_BLOB_LENGTH;
s.executeUpdate(
"CREATE TABLE T_MAIN(" +
"ID INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, " +
"V BLOB(" + blobsize + ") )");
PreparedStatement ps = prepareStatement(
"INSERT INTO T_MAIN(V) VALUES (?)");
int blobLen = blobsize;
LoopingAlphabetStream stream = new LoopingAlphabetStream(blobLen);
ps.setBinaryStream(1, stream, blobLen);
ps.executeUpdate();
ps.close();
s.executeUpdate("CREATE TABLE T_COPY ( V1 BLOB(" + blobsize +
"), V2 BLOB(" + blobsize + "))");
// This failed in the repro for DERBY-3645 solved as part of
// DERBY-4477:
s.executeUpdate("INSERT INTO T_COPY SELECT V, V FROM T_MAIN");
// Check that the two results are identical:
ResultSet rs = s.executeQuery("SELECT * FROM T_COPY");
rs.next();
InputStream is = rs.getBinaryStream(1);
stream.reset();
assertEquals(stream, is);
is = rs.getBinaryStream(2);
stream.reset();
assertEquals(stream, is);
rs.close();
// This failed in the repro for DERBY-3646 solved as part of
// DERBY-4477 (repro slightly rewoked here):
rs = s.executeQuery("SELECT 'I', V, ID, V from T_MAIN");
rs.next();
is = rs.getBinaryStream(2);
stream.reset();
assertEquals(stream, is);
is = rs.getBinaryStream(4);
stream.reset();
assertEquals(stream, is);
// clean up
stream.close();
is.close();
s.close();
rs.close();
rollback();
}
}