Description:
Adding a long SQL string to a java.sql.Statement batch will cause an ArrayIndexOutOfBoundsException if the length of the SQL statement is greater than the configured value of maxAllowedPacket.
A calculation is performed at https://github.com/mysql/mysql-connector-j/blob/release/8.0/src/main/user-impl/java/com/my... to determine if the next query will exceed the value of maxAllowedPacket.
If so, then the current query buffer is executed and the query buffer is reset.
However, the current query buffer is empty if the first batched SQL statement size calculation exceeds maxAllowedPacket.
The execution of the empty query causes the "counter" variable to be incremented beyond the length of the "updateCounts" array when calling processMultiCountsAndKeys and assigning the returned value to the "count" variable.
When the subsequent execution of the actual SQL statement occurs and processMultiCountsAndKeys is called again using the previously incremented "count" variable, an ArrayIndexOutOfBoundsException is thrown.
In our case, at StatementImpl line 998:
- the character encoding passed to the driver is UTF-8, which makes numberOfBytesPerChar=3
- the value of maxAllowedPacket is 1048576, which the driver is providing as the default
The attached test case illustrates the issue.
How to repeat:
// Maven POM
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>connectorj-bug</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.20</version>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>mysql</artifactId>
<version>1.14.3</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>11</source>
<target>11</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
// Java Test Class
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.HashMap;
import java.util.UUID;
import com.mysql.cj.jdbc.MysqlDataSource;
import org.junit.ClassRule;
import org.junit.Test;
import org.testcontainers.containers.MySQLContainer;
import org.testcontainers.shaded.org.apache.commons.lang.StringUtils;
public class ConnectorJBugTest {
@ClassRule
public static final MySQLContainer<?> mysql = new MySQLContainer<>()
.withConnectTimeoutSeconds(1)
.withUsername("cjbugttest")
.withPassword("cjbugttest")
.withDatabaseName("cjbugttest")
.withTmpFs(new HashMap<>(){{ put("/var/lib/mysql", ""); }});
@Test
public void test() throws SQLException {
final String url = String.format("jdbc:mysql://%s:%d/%s?allowMultiQueries=true&characterEncoding=UTF-8",
mysql.getContainerIpAddress(),
mysql.getFirstMappedPort(),
mysql.getDatabaseName());
final MysqlDataSource dataSource = new MysqlDataSource();
dataSource.setUrl(url);
dataSource.setUser(mysql.getUsername());
dataSource.setPassword(mysql.getPassword());
// Create table
try (final Connection connection = dataSource.getConnection()) {
try (final Statement statement = connection.createStatement()) {
statement.execute("CREATE TABLE `my_table` ("
+ " `my_table_id` varchar(36) NOT NULL,"
+ " `json_blob` json NOT NULL,"
+ " CONSTRAINT my_table_pk PRIMARY KEY (`my_table_id`)"
+ ") ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;");
}
}
// Insert
final String insert = String.format("insert into `my_table` (`my_table_id`, `json_blob`) values ('%s', '%s')",
UUID.randomUUID().toString(),
"{\"fieldA\": \"" + StringUtils.repeat("X", 175000) + "\"}");
try (final Connection connection = dataSource.getConnection()) {
try (final Statement statement = connection.createStatement()) {
statement.addBatch(insert);
statement.executeBatch();
}
}
}
}
Suggested fix:
At https://github.com/mysql/mysql-connector-j/blob/release/8.0/src/main/user-impl/java/com/my..., add a check to ensure queryBuf is not empty as part of the condition for executing.