Description:
MySQL Developers,
I'd like to report an unhandled StringIndexOutOfBoundsException in
Version 5.1.7 of MySQL Connector/J:
java.lang.StringIndexOutOfBoundsException: String index out of range: -1
at java.lang.String.substring(String.java:1938)
at com.mysql.jdbc.EscapeProcessor.processTimeToken(EscapeProcessor.java:353)
at com.mysql.jdbc.EscapeProcessor.escapeSQL(EscapeProcessor.java:257)
at com.mysql.jdbc.StatementImpl.executeUpdate(StatementImpl.java:1546)
at com.mysql.jdbc.StatementImpl.executeUpdate(StatementImpl.java:1524)
I will provide a small test case which reproduces the error. I have
experienced this error with the following configurations:
* Connector/J version 5.1.7 on Mac OS X 10.5.6, using MySQL 5.0.51b,
and java 1.6.0_07
* Connector/J version 5.1.5 on CentOS 4.4 (kernel: Linux
2.6.9-42.0.10.ELsmp #1 SMP Tue Feb 27 10:11:19 EST 2007 i686 athlon
i386 GNU/Linux), using MySQL 4.1.16 and java 1.6.0_06.
(The stack trace comes from the former Configuration, using
Connector/J 5.1.7 and java 1.6.0_07 -- "How to repeat" section uses
this configuration).
How to repeat:
Steps to reproduce:
(1) In MySQL's `test' database, create the following table:
------------------------------------------------------------------
CREATE TABLE `my_table` (
`c1` date NOT NULL,
`c2` varchar(32) NOT NULL default '',
`c3` varchar(5) NOT NULL default '',
`c4` tinyint(4) NOT NULL default '0',
`c5` datetime NOT NULL,
`c6` varchar(32) NOT NULL default '',
`c7` varchar(32) NOT NULL default '',
`c8` varchar(12) NOT NULL default '',
`c9` varchar(32) NOT NULL default '',
`c10` varchar(16) NOT NULL default '',
`c11` varchar(96) character set latin1 collate latin1_bin NOT NULL default 'N/A',
`c12` int(11) NOT NULL default '0',
`c13` varchar(32) NOT NULL default '',
`c14` int(10) unsigned default NULL,
`c15` int(10) unsigned default NULL,
`c16` varchar(28) character set latin1 collate latin1_bin NOT NULL default 'N/A',
`c17` tinyint(4) NOT NULL default '0',
`c18` decimal(9,6) default NULL,
KEY `datestamp_idx` (`c1`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;
------------------------------------------------------------------
(2) Save the following java code to the file 'Demo.java':
------------------------------------------------------------------
import java.io.IOException;
import java.io.BufferedReader;
import java.io.FileReader;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.DriverManager;
import java.sql.Statement;
public class Demo {
private String mFile;
private String mHost;
private String mUser;
private String mPasswd;
private String mDB;
private String mQuery;
private Demo(String file,
String host,
String user,
String passwd,
String db) {
this.mFile = file; /* read query from file */
this.mHost = host;
this.mUser = user;
this.mPasswd = passwd;
this.mDB = db;
}
public void doIt() throws IOException, SQLException, ClassNotFoundException {
mQuery = readQuery();
execQuery();
}
private String readQuery() throws IOException {
BufferedReader r = new BufferedReader(new FileReader(mFile));
StringBuilder sb = new StringBuilder();
String line = null;
while ((line = r.readLine()) != null) {
sb.append(line);
}
r.close();
return sb.toString();
}
private void execQuery() throws SQLException, ClassNotFoundException {
System.out.println("loading driver");
Class.forName("com.mysql.jdbc.Driver");
System.out.println("making connection");
Connection conn = DriverManager.getConnection(makeURL());
System.out.println("executing update");
Statement st = conn.createStatement();
st.executeUpdate(mQuery);
System.out.println("closing connection");
st.close();
conn.close();
}
private String makeURL() {
return "jdbc:mysql://" + mHost + "/" + mDB
+ "?user=" + mUser + "&password=" + mPasswd;
}
public static void main(String argv[]) {
if (argv.length != 5) {
System.err.println("usage: java Demo FILENAME HOST USER PASSWD DBNAME");
System.exit(1);
}
Demo demo = new Demo(argv[0], argv[1], argv[2], argv[3], argv[4]);
try {
demo.doIt();
}
catch (Throwable t) {
t.printStackTrace();
System.exit(1);
}
System.exit(0);
}
}
------------------------------------------------------------------
(3) Compile Demo.java, with the command line "javac Demo.java"
(4) Save the following SQL statement to a file named "test.sql"
------------------------------------------------------------------
insert into my_table
(c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, c11, c12, c13, c16, c17, c18)
values
('2009-01-20','c2','c3','4','2009-01-20 17:36:25.0','c6','c7','c8','c9','c10','\'c_11_+_c11\'','12','c13','c16','17','18.1'),
('2009-01-20','c2','c3','4','2009-01-20 19:37:05.0','c6','c7','c8','c9','c10','{tgv_c11\'s}/c11/c11','12','c13','c16','17','18.2')
------------------------------------------------------------------
(5) Run Demo.java with the command line below (substituting "HOST",
"USER", and "PASSWD" with your own hostname, username, and
password).
$ java -cp .:mysql-connector-java-5.1.7-bin.jar Demo test.sql HOST USER PASSWD test
loading driver
making connection
executing update
java.lang.StringIndexOutOfBoundsException: String index out of range: -1
at java.lang.String.substring(String.java:1938)
at com.mysql.jdbc.EscapeProcessor.processTimeToken(EscapeProcessor.java:353)
at com.mysql.jdbc.EscapeProcessor.escapeSQL(EscapeProcessor.java:257)
at com.mysql.jdbc.StatementImpl.executeUpdate(StatementImpl.java:1546)
at com.mysql.jdbc.StatementImpl.executeUpdate(StatementImpl.java:1524)
at Demo.execQuery(Demo.java:62)
at Demo.doIt(Demo.java:37)
at Demo.main(Demo.java:83)
It appears that the JDBC driver is trying to treat the quoted string
"{tgv_c11\'s}/c11/c11" as a timestamp escape.
Contrast this behavior with the following. Below, I'll source
"test.sql" with the mysql command line client, and the insert
statement will succeed.
$ mysql -u USER -pPASSWD -h HOST test
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 310
Server version: 5.0.51b-log MySQL Community Server (GPL)
Type 'help;' or '\h' for help. Type '\c' to clear the buffer.
test> source test.sql
Query OK, 2 rows affected (0.00 sec)
Records: 2 Duplicates: 0 Warnings: 0
test> select * from my_table;
oatmeal:test> select * from my_table;
+------------+----+----+----+---------------------+----+----+----+----+-----+---------------------+-----+-----+------+------+-----+-----+-----------+
| c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 | c9 | c10 | c11 | c12 | c13 | c14 | c15 | c16 | c17 | c18 |
+------------+----+----+----+---------------------+----+----+----+----+-----+---------------------+-----+-----+------+------+-----+-----+-----------+
| 2009-01-20 | c2 | c3 | 4 | 2009-01-20 17:36:25 | c6 | c7 | c8 | c9 | c10 | 'c_11_+_c11' | 12 | c13 | NULL | NULL | c16 | 17 | 18.100000 |
| 2009-01-20 | c2 | c3 | 4 | 2009-01-20 19:37:05 | c6 | c7 | c8 | c9 | c10 | {tgv_c11's}/c11/c11 | 12 | c13 | NULL | NULL | c16 | 17 | 18.200000 |
+------------+----+----+----+---------------------+----+----+----+----+-----+---------------------+-----+-----+------+------+-----+-----+-----------+
The behavior of the mysql command-line client appears correct. I
would expect the same behavior from Connector/J, but Connector/J
throws a StringIndexOutOfBoundsException instead.
Suggested fix:
Unfortunately, I have not found a workaround, but I would welcome any suggestions.