/*
* Copyright 2015-2017 the original author or authors.
*
* Licensed 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.glowroot.agent.plugin.jdbc;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Iterator;
import java.util.List;
import org.apache.commons.dbcp.DelegatingConnection;
import org.apache.commons.dbcp.DelegatingStatement;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import org.glowroot.agent.it.harness.AppUnderTest;
import org.glowroot.agent.it.harness.Container;
import org.glowroot.agent.it.harness.Containers;
import org.glowroot.agent.it.harness.TransactionMarker;
import org.glowroot.agent.plugin.jdbc.Connections.ConnectionType;
import org.glowroot.wire.api.model.TraceOuterClass.Trace;
import static org.assertj.core.api.Assertions.assertThat;
public class BatchIT {
private static final String PLUGIN_ID = "jdbc";
private static Container container;
private static boolean driverCapturesBatchRows =
Connections.getConnectionType() != ConnectionType.ORACLE;
@BeforeClass
public static void setUp() throws Exception {
container = Containers.create();
}
@AfterClass
public static void tearDown() throws Exception {
container.close();
}
@After
public void afterEachTest() throws Exception {
container.checkAndReset();
}
@Test
public void testBatchPreparedStatement() throws Exception {
// when
Trace trace = container.execute(ExecuteBatchPreparedStatement.class);
// then
Iterator<Trace.Entry> i = trace.getEntryList().iterator();
List<Trace.SharedQueryText> sharedQueryTexts = trace.getSharedQueryTextList();
Trace.Entry entry = i.next();
assertThat(entry.getDepth()).isEqualTo(0);
assertThat(entry.getMessage()).isEmpty();
assertThat(sharedQueryTexts.get(entry.getQueryEntryMessage().getSharedQueryTextIndex())
.getFullText()).isEqualTo("insert into employee (name) values (?)");
assertThat(entry.getQueryEntryMessage().getPrefix()).isEqualTo("jdbc execution: 3 x ");
if (driverCapturesBatchRows) {
assertThat(entry.getQueryEntryMessage().getSuffix())
.isEqualTo(" ['huckle'] ['sally'] ['sally'] => 3 rows");
} else {
assertThat(entry.getQueryEntryMessage().getSuffix())
.isEqualTo(" ['huckle'] ['sally'] ['sally']");
}
entry = i.next();
assertThat(entry.getDepth()).isEqualTo(0);
assertThat(entry.getMessage()).isEmpty();
assertThat(sharedQueryTexts.get(entry.getQueryEntryMessage().getSharedQueryTextIndex())
.getFullText()).isEqualTo("insert into employee (name) values (?)");
assertThat(entry.getQueryEntryMessage().getPrefix()).isEqualTo("jdbc execution: 2 x ");
if (driverCapturesBatchRows) {
assertThat(entry.getQueryEntryMessage().getSuffix())
.isEqualTo(" ['lowly'] ['pig will'] => 2 rows");
} else {
assertThat(entry.getQueryEntryMessage().getSuffix())
.isEqualTo(" ['lowly'] ['pig will']");
}
assertThat(i.hasNext()).isFalse();
}
@Test
public void testBatchPreparedExceedingLimitStatement() throws Exception {
// when
Trace trace = container.execute(ExecuteBatchExceedingLimitPreparedStatement.class);
// then
Iterator<Trace.Entry> i = trace.getEntryList().iterator();
List<Trace.SharedQueryText> sharedQueryTexts = trace.getSharedQueryTextList();
Trace.Entry entry = i.next();
assertThat(entry.getDepth()).isEqualTo(0);
assertThat(entry.getMessage()).isEmpty();
assertThat(sharedQueryTexts.get(entry.getQueryEntryMessage().getSharedQueryTextIndex())
.getFullText()).isEqualTo("insert into employee (name) values (?)");
assertThat(entry.getQueryEntryMessage().getPrefix()).isEqualTo("jdbc execution: 2002 x ");
StringBuilder sb = new StringBuilder();
for (int j = 0; j < 1000; j++) {
sb.append(" ['name");
sb.append(j);
sb.append("']");
}
if (driverCapturesBatchRows) {
sb.append(" ... => 2002 rows");
} else {
sb.append(" ...");
}
assertThat(entry.getQueryEntryMessage().getSuffix()).isEqualTo(sb.toString());
assertThat(i.hasNext()).isFalse();
}
@Test
public void testBatchPreparedStatementWithoutCaptureBindParams() throws Exception {
// given
container.getConfigService().setPluginProperty(PLUGIN_ID, "captureBindParameters", false);
// when
Trace trace = container.execute(ExecuteBatchPreparedStatement.class);
// then
Iterator<Trace.Entry> i = trace.getEntryList().iterator();
List<Trace.SharedQueryText> sharedQueryTexts = trace.getSharedQueryTextList();
Trace.Entry entry = i.next();
assertThat(entry.getDepth()).isEqualTo(0);
assertThat(entry.getMessage()).isEmpty();
assertThat(sharedQueryTexts.get(entry.getQueryEntryMessage().getSharedQueryTextIndex())
.getFullText()).isEqualTo("insert into employee (name) values (?)");
assertThat(entry.getQueryEntryMessage().getPrefix()).isEqualTo("jdbc execution: 3 x ");
if (driverCapturesBatchRows) {
assertThat(entry.getQueryEntryMessage().getSuffix()).isEqualTo(" => 3 rows");
} else {
assertThat(entry.getQueryEntryMessage().getSuffix()).isEmpty();
}
entry = i.next();
assertThat(entry.getDepth()).isEqualTo(0);
assertThat(entry.getMessage()).isEmpty();
assertThat(sharedQueryTexts.get(entry.getQueryEntryMessage().getSharedQueryTextIndex())
.getFullText()).isEqualTo("insert into employee (name) values (?)");
assertThat(entry.getQueryEntryMessage().getPrefix()).isEqualTo("jdbc execution: 2 x ");
if (driverCapturesBatchRows) {
assertThat(entry.getQueryEntryMessage().getSuffix()).isEqualTo(" => 2 rows");
} else {
assertThat(entry.getQueryEntryMessage().getSuffix()).isEmpty();
}
assertThat(i.hasNext()).isFalse();
}
@Test
public void testBatchPreparedStatementWithoutClear() throws Exception {
// when
Trace trace = container.execute(ExecuteBatchPreparedStatementWithoutClear.class);
// then
Iterator<Trace.Entry> i = trace.getEntryList().iterator();
List<Trace.SharedQueryText> sharedQueryTexts = trace.getSharedQueryTextList();
Trace.Entry entry = i.next();
assertThat(entry.getDepth()).isEqualTo(0);
assertThat(entry.getMessage()).isEmpty();
assertThat(sharedQueryTexts.get(entry.getQueryEntryMessage().getSharedQueryTextIndex())
.getFullText()).isEqualTo("insert into employee (name) values (?)");
assertThat(entry.getQueryEntryMessage().getPrefix()).isEqualTo("jdbc execution: 2 x ");
if (driverCapturesBatchRows) {
assertThat(entry.getQueryEntryMessage().getSuffix())
.isEqualTo(" ['huckle'] ['sally'] => 2 rows");
} else {
assertThat(entry.getQueryEntryMessage().getSuffix())
.isEqualTo(" ['huckle'] ['sally']");
}
entry = i.next();
assertThat(entry.getDepth()).isEqualTo(0);
assertThat(entry.getMessage()).isEmpty();
assertThat(sharedQueryTexts.get(entry.getQueryEntryMessage().getSharedQueryTextIndex())
.getFullText()).isEqualTo("insert into employee (name) values (?)");
assertThat(entry.getQueryEntryMessage().getPrefix()).isEqualTo("jdbc execution: 2 x ");
if (driverCapturesBatchRows) {
assertThat(entry.getQueryEntryMessage().getSuffix())
.isEqualTo(" ['lowly'] ['pig will'] => 2 rows");
} else {
assertThat(entry.getQueryEntryMessage().getSuffix())
.isEqualTo(" ['lowly'] ['pig will']");
}
assertThat(i.hasNext()).isFalse();
}
@Test
public void testBatchPreparedStatementWithoutClearWithoutCaptureBindParams() throws Exception {
// given
container.getConfigService().setPluginProperty(PLUGIN_ID, "captureBindParameters", false);
// when
Trace trace = container.execute(ExecuteBatchPreparedStatementWithoutClear.class);
// then
Iterator<Trace.Entry> i = trace.getEntryList().iterator();
List<Trace.SharedQueryText> sharedQueryTexts = trace.getSharedQueryTextList();
Trace.Entry entry = i.next();
assertThat(entry.getDepth()).isEqualTo(0);
assertThat(entry.getMessage()).isEmpty();
assertThat(sharedQueryTexts.get(entry.getQueryEntryMessage().getSharedQueryTextIndex())
.getFullText()).isEqualTo("insert into employee (name) values (?)");
assertThat(entry.getQueryEntryMessage().getPrefix()).isEqualTo("jdbc execution: 2 x ");
if (driverCapturesBatchRows) {
assertThat(entry.getQueryEntryMessage().getSuffix()).isEqualTo(" => 2 rows");
} else {
assertThat(entry.getQueryEntryMessage().getSuffix()).isEmpty();
}
entry = i.next();
assertThat(entry.getDepth()).isEqualTo(0);
assertThat(entry.getMessage()).isEmpty();
assertThat(sharedQueryTexts.get(entry.getQueryEntryMessage().getSharedQueryTextIndex())
.getFullText()).isEqualTo("insert into employee (name) values (?)");
assertThat(entry.getQueryEntryMessage().getPrefix()).isEqualTo("jdbc execution: 2 x ");
if (driverCapturesBatchRows) {
assertThat(entry.getQueryEntryMessage().getSuffix()).isEqualTo(" => 2 rows");
} else {
assertThat(entry.getQueryEntryMessage().getSuffix()).isEmpty();
}
assertThat(i.hasNext()).isFalse();
}
@Test
public void testBatchStatement() throws Exception {
// when
Trace trace = container.execute(ExecuteBatchStatement.class);
// then
Iterator<Trace.Entry> i = trace.getEntryList().iterator();
List<Trace.SharedQueryText> sharedQueryTexts = trace.getSharedQueryTextList();
Trace.Entry entry = i.next();
assertThat(entry.getDepth()).isEqualTo(0);
assertThat(entry.getMessage()).isEmpty();
assertThat(sharedQueryTexts.get(entry.getQueryEntryMessage().getSharedQueryTextIndex())
.getFullText()).isEqualTo("[batch] insert into employee (name) values ('huckle'),"
+ " insert into employee (name) values ('sally')");
assertThat(entry.getQueryEntryMessage().getPrefix()).isEqualTo("jdbc execution: ");
assertThat(entry.getQueryEntryMessage().getSuffix()).isEqualTo(" => 2 rows");
entry = i.next();
assertThat(entry.getDepth()).isEqualTo(0);
assertThat(entry.getMessage()).isEmpty();
assertThat(sharedQueryTexts.get(entry.getQueryEntryMessage().getSharedQueryTextIndex())
.getFullText()).isEqualTo("[batch] insert into employee (name) values ('lowly'),"
+ " insert into employee (name) values ('pig will')");
assertThat(entry.getQueryEntryMessage().getPrefix()).isEqualTo("jdbc execution: ");
assertThat(entry.getQueryEntryMessage().getSuffix()).isEqualTo(" => 2 rows");
assertThat(i.hasNext()).isFalse();
}
@Test
public void testBatchStatementNull() throws Exception {
// when
Trace trace = container.execute(BatchStatementNull.class);
// then
Iterator<Trace.Entry> i = trace.getEntryList().iterator();
List<Trace.SharedQueryText> sharedQueryTexts = trace.getSharedQueryTextList();
Trace.Entry entry = i.next();
assertThat(entry.getDepth()).isEqualTo(0);
assertThat(entry.getMessage()).isEmpty();
assertThat(sharedQueryTexts.get(entry.getQueryEntryMessage().getSharedQueryTextIndex())
.getFullText()).isEqualTo("[batch] insert into employee (name) values ('1')");
assertThat(entry.getQueryEntryMessage().getPrefix()).isEqualTo("jdbc execution: ");
assertThat(entry.getQueryEntryMessage().getSuffix()).isEqualTo(" => 1 row");
assertThat(i.hasNext()).isFalse();
}
@Test
public void testBatchStatementWithNoBatches() throws Exception {
// when
Trace trace = container.execute(ExecuteBatchStatementWithNoBatches.class);
// then
Iterator<Trace.Entry> i = trace.getEntryList().iterator();
List<Trace.SharedQueryText> sharedQueryTexts = trace.getSharedQueryTextList();
Trace.Entry entry = i.next();
assertThat(entry.getDepth()).isEqualTo(0);
assertThat(entry.getMessage()).isEmpty();
assertThat(sharedQueryTexts.get(entry.getQueryEntryMessage().getSharedQueryTextIndex())
.getFullText()).isEqualTo("[empty batch]");
assertThat(entry.getQueryEntryMessage().getPrefix()).isEqualTo("jdbc execution: ");
assertThat(entry.getQueryEntryMessage().getSuffix()).isEmpty();
assertThat(i.hasNext()).isFalse();
}
@Test
public void testBatchStatementWithoutClear() throws Exception {
// when
Trace trace = container.execute(ExecuteBatchStatementWithoutClear.class);
// then
Iterator<Trace.Entry> i = trace.getEntryList().iterator();
List<Trace.SharedQueryText> sharedQueryTexts = trace.getSharedQueryTextList();
Trace.Entry entry = i.next();
assertThat(entry.getDepth()).isEqualTo(0);
assertThat(entry.getMessage()).isEmpty();
assertThat(sharedQueryTexts.get(entry.getQueryEntryMessage().getSharedQueryTextIndex())
.getFullText()).isEqualTo("[batch] insert into employee (name) values ('huckle'),"
+ " insert into employee (name) values ('sally')");
assertThat(entry.getQueryEntryMessage().getPrefix()).isEqualTo("jdbc execution: ");
assertThat(entry.getQueryEntryMessage().getSuffix()).isEqualTo(" => 2 rows");
entry = i.next();
assertThat(entry.getDepth()).isEqualTo(0);
assertThat(entry.getMessage()).isEmpty();
assertThat(sharedQueryTexts.get(entry.getQueryEntryMessage().getSharedQueryTextIndex())
.getFullText()).isEqualTo("[batch] insert into employee (name) values ('lowly'),"
+ " insert into employee (name) values ('pig will')");
assertThat(entry.getQueryEntryMessage().getPrefix()).isEqualTo("jdbc execution: ");
assertThat(entry.getQueryEntryMessage().getSuffix()).isEqualTo(" => 2 rows");
assertThat(i.hasNext()).isFalse();
}
public static class ExecuteBatchPreparedStatement implements AppUnderTest, TransactionMarker {
private Connection connection;
@Override
public void executeApp() throws Exception {
connection = Connections.createConnection();
connection.setAutoCommit(false);
try {
transactionMarker();
} finally {
Connections.closeConnection(connection);
}
}
@Override
public void transactionMarker() throws Exception {
PreparedStatement preparedStatement =
connection.prepareStatement("insert into employee (name) values (?)");
try {
preparedStatement.setString(1, "huckle");
preparedStatement.addBatch();
preparedStatement.setString(1, "sally");
preparedStatement.addBatch();
// add batch without re-setting params
preparedStatement.addBatch();
preparedStatement.executeBatch();
preparedStatement.clearBatch();
preparedStatement.clearBatch();
preparedStatement.setString(1, "lowly");
preparedStatement.addBatch();
preparedStatement.setString(1, "pig will");
preparedStatement.addBatch();
preparedStatement.executeBatch();
} finally {
preparedStatement.close();
}
}
}
public static class ExecuteBatchExceedingLimitPreparedStatement
implements AppUnderTest, TransactionMarker {
private Connection connection;
@Override
public void executeApp() throws Exception {
connection = Connections.createConnection();
connection.setAutoCommit(false);
try {
transactionMarker();
} finally {
Connections.closeConnection(connection);
}
}
@Override
public void transactionMarker() throws Exception {
PreparedStatement preparedStatement =
connection.prepareStatement("insert into employee (name) values (?)");
try {
for (int i = 0; i < 2002; i++) {
preparedStatement.setString(1, "name" + i);
preparedStatement.addBatch();
}
preparedStatement.executeBatch();
} finally {
preparedStatement.close();
}
}
}
public static class ExecuteBatchPreparedStatementWithoutClear
implements AppUnderTest, TransactionMarker {
private Connection connection;
@Override
public void executeApp() throws Exception {
connection = Connections.createConnection();
connection.setAutoCommit(false);
try {
transactionMarker();
} finally {
Connections.closeConnection(connection);
}
}
@Override
public void transactionMarker() throws Exception {
PreparedStatement preparedStatement =
connection.prepareStatement("insert into employee (name) values (?)");
try {
preparedStatement.setString(1, "huckle");
preparedStatement.addBatch();
preparedStatement.setString(1, "sally");
preparedStatement.addBatch();
preparedStatement.executeBatch();
// intentionally not calling preparedStatement.clearBatch()
preparedStatement.setString(1, "lowly");
preparedStatement.addBatch();
preparedStatement.setString(1, "pig will");
preparedStatement.addBatch();
preparedStatement.executeBatch();
} finally {
preparedStatement.close();
}
}
}
public static class ExecuteBatchStatement implements AppUnderTest, TransactionMarker {
private Connection connection;
@Override
public void executeApp() throws Exception {
connection = Connections.createConnection();
connection.setAutoCommit(false);
try {
transactionMarker();
} finally {
Connections.closeConnection(connection);
}
}
@Override
public void transactionMarker() throws Exception {
Statement statement = connection.createStatement();
try {
statement.addBatch("insert into employee (name) values ('huckle')");
statement.addBatch("insert into employee (name) values ('sally')");
statement.executeBatch();
statement.clearBatch();
statement.addBatch("insert into employee (name) values ('lowly')");
statement.addBatch("insert into employee (name) values ('pig will')");
statement.executeBatch();
} finally {
statement.close();
}
}
}
public static class BatchStatementNull implements AppUnderTest, TransactionMarker {
private Connection delegatingConnection;
@Override
public void executeApp() throws Exception {
Connection connection = Connections.createConnection();
delegatingConnection = new DelegatingConnection(connection) {
@Override
public Statement createStatement() throws SQLException {
return new DelegatingStatement(this, super.createStatement()) {
@Override
public void addBatch(String sql) throws SQLException {
super.addBatch("insert into employee (name) values ('1')");
}
};
}
};
try {
transactionMarker();
} finally {
Connections.closeConnection(connection);
}
}
@Override
public void transactionMarker() throws Exception {
Statement statement = delegatingConnection.createStatement();
try {
statement.addBatch(null);
statement.executeBatch();
} finally {
statement.close();
}
}
}
public static class ExecuteBatchStatementWithNoBatches
implements AppUnderTest, TransactionMarker {
private Connection connection;
@Override
public void executeApp() throws Exception {
connection = Connections.createConnection();
connection.setAutoCommit(false);
try {
transactionMarker();
} finally {
Connections.closeConnection(connection);
}
}
@Override
public void transactionMarker() throws Exception {
Statement statement = connection.createStatement();
try {
statement.executeBatch();
} finally {
statement.close();
}
}
}
public static class ExecuteBatchStatementWithoutClear
implements AppUnderTest, TransactionMarker {
private Connection connection;
@Override
public void executeApp() throws Exception {
connection = Connections.createConnection();
connection.setAutoCommit(false);
try {
transactionMarker();
} finally {
Connections.closeConnection(connection);
}
}
@Override
public void transactionMarker() throws Exception {
Statement statement = connection.createStatement();
try {
statement.addBatch("insert into employee (name) values ('huckle')");
statement.addBatch("insert into employee (name) values ('sally')");
statement.executeBatch();
// intentionally not calling statement.clearBatch()
statement.addBatch("insert into employee (name) values ('lowly')");
statement.addBatch("insert into employee (name) values ('pig will')");
statement.executeBatch();
} finally {
statement.close();
}
}
}
}