/*
* Copyright (c) 2003, PostgreSQL Global Development Group
* See the LICENSE file in the project root for more information.
*/
package org.postgresql.jdbc;
import org.postgresql.PGProperty;
import org.postgresql.core.ParameterList;
import org.postgresql.core.Query;
import org.postgresql.core.v3.BatchedQuery;
import org.postgresql.test.TestUtil;
import org.postgresql.test.jdbc2.BaseTest;
import org.postgresql.test.jdbc2.BatchExecuteTest;
import java.lang.reflect.Method;
import java.sql.Date;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Properties;
/**
* This object tests the internals of the BatchedStatementDecorator during
* execution. Rather than rely on testing at the jdbc api layer.
* on.
*/
public class DeepBatchedInsertStatementTest extends BaseTest {
public DeepBatchedInsertStatementTest(String name) {
super(name);
}
/*
* Set up the fixture for this testcase: a connection to a database with a
* table for this test.
*/
protected void setUp() throws Exception {
super.setUp();
Statement stmt = con.createStatement();
/*
* Drop the test table if it already exists for some reason. It is not an
* error if it doesn't exist.
*/
TestUtil.createTable(con, "testbatch", "pk INTEGER, col1 INTEGER");
TestUtil.createTable(con, "testunspecified", "pk INTEGER, bday TIMESTAMP");
stmt.executeUpdate("INSERT INTO testbatch VALUES (1, 0)");
stmt.close();
/*
* Generally recommended with batch updates. By default we run all tests in
* this test case with autoCommit disabled.
*/
con.setAutoCommit(false);
}
// Tear down the fixture for this test case.
protected void tearDown() throws SQLException {
TestUtil.dropTable(con, "testbatch");
TestUtil.dropTable(con, "testunspecified");
super.tearDown();
}
@Override
protected void updateProperties(Properties props) {
PGProperty.REWRITE_BATCHED_INSERTS.set(props, true);
forceBinary(props);
}
public void testDeepInternalsBatchedQueryDecorator() throws Exception {
PgPreparedStatement pstmt = null;
try {
pstmt = (PgPreparedStatement) con.prepareStatement("INSERT INTO testbatch VALUES (?,?)");
pstmt.setInt(1, 1);
pstmt.setInt(2, 2);
pstmt.addBatch(); // initial pass
pstmt.setInt(1, 3);
pstmt.setInt(2, 4);
pstmt.addBatch();// preparedQuery should be wrapped
BatchedQuery[] bqds;
bqds = transformBQD(pstmt);
assertEquals(2, getBatchSize(bqds));
pstmt.setInt(1, 5);
pstmt.setInt(2, 6);
pstmt.addBatch();
bqds = transformBQD(pstmt);
assertEquals(3, getBatchSize(bqds));
BatchExecuteTest.assertSimpleInsertBatch(3, pstmt.executeBatch());
bqds = transformBQD(pstmt);
assertEquals(0, getBatchSize(bqds));
pstmt.setInt(1, 1);
pstmt.setInt(2, 2);
pstmt.addBatch();
bqds = transformBQD(pstmt);
assertEquals(1, getBatchSize(bqds));
pstmt.setInt(1, 3);
pstmt.setInt(2, 4);
pstmt.addBatch();
bqds = transformBQD(pstmt);
assertEquals(2, getBatchSize(bqds));
pstmt.setInt(1, 5);
pstmt.setInt(2, 6);
pstmt.addBatch();
bqds = transformBQD(pstmt);
assertEquals(3, getBatchSize(bqds));
BatchExecuteTest.assertSimpleInsertBatch(3, pstmt.executeBatch());
pstmt.setInt(1, 1);
pstmt.setInt(2, 2);
pstmt.addBatch();
bqds = transformBQD(pstmt);
assertEquals(1, getBatchSize(bqds));
pstmt.setInt(1, 3);
pstmt.setInt(2, 4);
pstmt.addBatch();
bqds = transformBQD(pstmt);
assertEquals(2, getBatchSize(bqds));
pstmt.setInt(1, 5);
pstmt.setInt(2, 6);
pstmt.addBatch();
bqds = transformBQD(pstmt);
assertEquals(3, getBatchSize(bqds));
pstmt.setInt(1, 7);
pstmt.setInt(2, 8);
pstmt.addBatch();
bqds = transformBQD(pstmt);
assertEquals(4, getBatchSize(bqds));
BatchExecuteTest.assertSimpleInsertBatch(4, pstmt.executeBatch());
pstmt.setInt(1, 1);
pstmt.setInt(2, 2);
pstmt.addBatch();
bqds = transformBQD(pstmt);
assertEquals(1, getBatchSize(bqds));
pstmt.setInt(1, 3);
pstmt.setInt(2, 4);
pstmt.addBatch();
bqds = transformBQD(pstmt);
assertEquals(2, getBatchSize(bqds));
pstmt.setInt(1, 5);
pstmt.setInt(2, 6);
pstmt.addBatch();
bqds = transformBQD(pstmt);
assertEquals(3, getBatchSize(bqds));
BatchExecuteTest.assertSimpleInsertBatch(3, pstmt.executeBatch());
pstmt.setInt(1, 1);
pstmt.setInt(2, 2);
pstmt.addBatch();
bqds = transformBQD(pstmt);
assertEquals(1, getBatchSize(bqds));
pstmt.setInt(1, 3);
pstmt.setInt(2, 4);
pstmt.addBatch();
bqds = transformBQD(pstmt);
assertEquals(2, getBatchSize(bqds));
pstmt.setInt(1, 5);
pstmt.setInt(2, 6);
pstmt.addBatch();
bqds = transformBQD(pstmt);
assertEquals(3, getBatchSize(bqds));
BatchExecuteTest.assertSimpleInsertBatch(3, pstmt.executeBatch());
pstmt.setInt(1, 1);
pstmt.setInt(2, 2);
pstmt.addBatch();
bqds = transformBQD(pstmt);
assertEquals(1, getBatchSize(bqds));
pstmt.setInt(1, 3);
pstmt.setInt(2, 4);
pstmt.addBatch();
bqds = transformBQD(pstmt);
assertEquals(2, getBatchSize(bqds));
BatchExecuteTest.assertSimpleInsertBatch(2, pstmt.executeBatch());
} finally {
TestUtil.closeQuietly(pstmt);
}
}
/**
*
*/
public void testUnspecifiedParameterType() throws Exception {
PgPreparedStatement pstmt = null;
try {
pstmt = (PgPreparedStatement) con
.prepareStatement("INSERT INTO testunspecified VALUES (?,?)");
pstmt.setInt(1, 1);
pstmt.setDate(2, new Date(1));
pstmt.addBatch();
pstmt.setInt(1, 2);
pstmt.setDate(2, new Date(2));
pstmt.addBatch();
BatchExecuteTest.assertSimpleInsertBatch(2, pstmt.executeBatch());
pstmt.setInt(1, 1);
pstmt.setDate(2, new Date(3));
pstmt.addBatch();
pstmt.setInt(1, 2);
pstmt.setDate(2, new Date(4));
pstmt.addBatch();
BatchExecuteTest.assertSimpleInsertBatch(2, pstmt.executeBatch());
} finally {
TestUtil.closeQuietly(pstmt);
}
}
/**
* Test to check the statement can provide the necessary number of prepared
* type fields. This is after running with a batch size of 1.
*/
public void testVaryingTypeCounts() throws SQLException {
PgPreparedStatement pstmt = null;
try {
pstmt = (PgPreparedStatement)con.prepareStatement("INSERT INTO testunspecified VALUES (?,?)");
pstmt.setInt(1, 1);
pstmt.setDate(2, new Date(1));
pstmt.addBatch();
BatchExecuteTest.assertSimpleInsertBatch(1, pstmt.executeBatch());
pstmt.setInt(1, 1);
pstmt.setDate(2, new Date(2));
pstmt.addBatch();
pstmt.setInt(1, 2);
pstmt.setDate(2, new Date(3));
pstmt.addBatch();
pstmt.setInt(1, 3);
pstmt.setDate(2, new Date(4));
pstmt.addBatch();
pstmt.setInt(1, 4);
pstmt.setDate(2, new Date(5));
pstmt.addBatch();
BatchExecuteTest.assertSimpleInsertBatch(4, pstmt.executeBatch());
} finally {
TestUtil.closeQuietly(pstmt);
}
}
/**
* This method triggers the transformation of single batches to multi batches.
*
* @param ps PgPreparedStatement statement that will contain the field
* @return BatchedQueryDecorator[] queries after conversion
* @throws Exception fault raised when the field cannot be accessed
*/
private BatchedQuery[] transformBQD(PgPreparedStatement ps) throws Exception {
// We store collections that get replace on the statement
ArrayList<Query> batchStatements = ps.batchStatements;
ArrayList<ParameterList> batchParameters = ps.batchParameters;
ps.transformQueriesAndParameters();
BatchedQuery[] bqds = ps.batchStatements.toArray(new BatchedQuery[0]);
// Restore collections on the statement.
ps.batchStatements = batchStatements;
ps.batchParameters = batchParameters;
return bqds;
}
/**
* Get the total batch size of multi batches.
*
* @param bqds the converted queries
* @return the total batch size
*/
private int getBatchSize(BatchedQuery[] bqds) {
int total = 0;
for (BatchedQuery bqd : bqds) {
total += bqd.getBatchSize();
}
return total;
}
/**
* Access the encoded statement name field.
* Again using reflection to gain access to a private field member
* @param bqd BatchedQueryDecorator object on which field is present
* @return byte[] array of bytes that represent the statement name
* when encoded
* @throws Exception fault raised if access to field not possible
*/
private byte[] getEncodedStatementName(BatchedQuery bqd)
throws Exception {
Class<?> clazz = Class.forName("org.postgresql.core.v3.SimpleQuery");
Method mESN = clazz.getDeclaredMethod("getEncodedStatementName");
mESN.setAccessible(true);
return (byte[]) mESN.invoke(bqd);
}
}