From 830e29ca6548b2292c8d57ce8de905758aae7309 Mon Sep 17 00:00:00 2001 From: Rasmus Johansson Date: Mon, 13 Apr 2015 12:35:47 +0300 Subject: [PATCH] CONJ-142: Using a semicolon in a string with "rewriteBatchedStatements=true" fails Added test case for CONJ-142 and made corrections to getInsertIncipit in MySQLStatement Also changed from splitting to looking for first question mark when parsing JDBC url. --- src/main/java/org/mariadb/jdbc/JDBCUrl.java | 6 +- .../org/mariadb/jdbc/MySQLConnection.java | 4 +- .../java/org/mariadb/jdbc/MySQLStatement.java | 25 +++++-- src/test/java/org/mariadb/jdbc/BaseTest.java | 12 +++- src/test/java/org/mariadb/jdbc/MultiTest.java | 66 ++++++++++++++++++- 5 files changed, 98 insertions(+), 15 deletions(-) diff --git a/src/main/java/org/mariadb/jdbc/JDBCUrl.java b/src/main/java/org/mariadb/jdbc/JDBCUrl.java index 9b1153776..f9aded830 100644 --- a/src/main/java/org/mariadb/jdbc/JDBCUrl.java +++ b/src/main/java/org/mariadb/jdbc/JDBCUrl.java @@ -93,11 +93,9 @@ private static JDBCUrl parseConnectorJUrl(String url) { //check if there are parameters if (database.indexOf('?') > -1) { - String[] halfs = database.split("\\?"); + String[] credentials = database.substring(database.indexOf('?'), database.length()).split("&"); - database = halfs[0]; - - String[] credentials = halfs[1].split("&"); + database = database.substring(0, database.indexOf('?')); for (int i = 0; i < credentials.length; i++) { diff --git a/src/main/java/org/mariadb/jdbc/MySQLConnection.java b/src/main/java/org/mariadb/jdbc/MySQLConnection.java index 61811cb66..9048cb91c 100644 --- a/src/main/java/org/mariadb/jdbc/MySQLConnection.java +++ b/src/main/java/org/mariadb/jdbc/MySQLConnection.java @@ -60,7 +60,7 @@ WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWIS import java.util.concurrent.Executor; -public final class MySQLConnection implements Connection { +public final class MySQLConnection implements Connection { /** * the protocol to communicate with. */ @@ -88,7 +88,7 @@ public final class MySQLConnection implements Connection { * * @param protocol the protocol to use. */ - private MySQLConnection( MySQLProtocol protocol) { + private MySQLConnection(MySQLProtocol protocol) { this.protocol = protocol; clientInfoProperties = protocol.getInfo(); } diff --git a/src/main/java/org/mariadb/jdbc/MySQLStatement.java b/src/main/java/org/mariadb/jdbc/MySQLStatement.java index 1bbb8da92..bc8e4a5b6 100644 --- a/src/main/java/org/mariadb/jdbc/MySQLStatement.java +++ b/src/main/java/org/mariadb/jdbc/MySQLStatement.java @@ -1134,13 +1134,28 @@ private void isInsertRewriteable(String sql) { */ protected int getInsertIncipit(String sql) { String sqlUpper = sql.toUpperCase(); - if (! sqlUpper.startsWith("INSERT") - || sqlUpper.indexOf(";") != -1) { + + if (! sqlUpper.startsWith("INSERT")) return -1; - } + int idx = sqlUpper.indexOf(" VALUE"); - int index = sqlUpper.indexOf("(", idx); - return index; + int startBracket = sqlUpper.indexOf("(", idx); + int endBracket = sqlUpper.indexOf(")", startBracket); + + // Check for semicolons. Allow them inside the VALUES() brackets, otherwise return -1 + // there can be multiple, so let's loop through them + + int semicolonPos = sqlUpper.indexOf(';'); + + while (semicolonPos > -1) + { + if (semicolonPos < startBracket || semicolonPos > endBracket) + return -1; + + semicolonPos = sqlUpper.indexOf(';', semicolonPos + 1); + } + + return startBracket; } /** diff --git a/src/test/java/org/mariadb/jdbc/BaseTest.java b/src/test/java/org/mariadb/jdbc/BaseTest.java index 464f1f54a..75945f5b8 100644 --- a/src/test/java/org/mariadb/jdbc/BaseTest.java +++ b/src/test/java/org/mariadb/jdbc/BaseTest.java @@ -29,11 +29,15 @@ public class BaseTest { public static void beforeClassBaseTest() { String url = System.getProperty("dbUrl", mDefUrl); JDBCUrl jdbcUrl = JDBCUrl.parse(url); + hostname = jdbcUrl.getHostname(); port = jdbcUrl.getPort(); database = jdbcUrl.getDatabase(); username = jdbcUrl.getUsername(); password = jdbcUrl.getPassword(); + + logInfo("Properties parsed from JDBC URL - hostname: " + hostname + ", port: " + port + ", database: " + database + ", username: " + username + ", password: " + password); + if (database != null && "".equals(username)) { String[] tokens = database.contains("?") ? database.split("\\?") : null; if (tokens != null) { @@ -109,7 +113,7 @@ protected void setConnection(Map props) throws SQLException { openConnection(connU, info); } protected void setConnection(Properties info) throws SQLException { - openConnection(connU, info); + openConnection(connURI, info); } protected void setConnection(String parameters) throws SQLException { openConnection(connURI + parameters, null); @@ -216,4 +220,10 @@ void requireMinimumVersion(int major, int minor) throws SQLException { (dbMajor == major && dbMinor >= minor)); } + + // common function for logging information + static void logInfo(String message) + { + System.out.println(message); + } } diff --git a/src/test/java/org/mariadb/jdbc/MultiTest.java b/src/test/java/org/mariadb/jdbc/MultiTest.java index a76d9351b..7ee931f34 100644 --- a/src/test/java/org/mariadb/jdbc/MultiTest.java +++ b/src/test/java/org/mariadb/jdbc/MultiTest.java @@ -5,7 +5,9 @@ import org.junit.Test; import java.sql.*; +import java.util.Properties; +import junit.framework.Assert; import static org.junit.Assert.*; @@ -24,10 +26,13 @@ public static void beforeClassMultiTest() throws SQLException { Statement st = connection.createStatement(); st.executeUpdate("drop table if exists t1"); st.executeUpdate("drop table if exists t2"); - st.executeUpdate("create table t2(id int, test varchar(100))"); + st.executeUpdate("drop table if exists t3"); st.executeUpdate("create table t1(id int, test varchar(100))"); + st.executeUpdate("create table t2(id int, test varchar(100))"); + st.executeUpdate("create table t3(message text)"); st.execute("insert into t1 values(1,'a'),(2,'a')"); st.execute("insert into t2 values(1,'a'),(2,'a')"); + st.execute("insert into t3 values('hello')"); } @AfterClass @@ -36,7 +41,7 @@ public static void afterClass() throws SQLException { Statement st = connection.createStatement(); st.executeUpdate("drop table if exists t1"); st.executeUpdate("drop table if exists t2"); - + st.executeUpdate("drop table if exists t3"); } catch (Exception e) { // eat } @@ -151,7 +156,9 @@ public void setMaxRowsMulti() throws Exception { @Test public void rewriteBatchedStatementsInsertTest() throws SQLException { // set the rewrite batch statements parameter - setConnection("&rewriteBatchedStatements=true"); + Properties props = new Properties(); + props.setProperty("rewriteBatchedStatements", "true"); + connection.setClientInfo(props); int cycles = 3000; PreparedStatement preparedStatement = prepareStatementBatch(cycles); @@ -176,6 +183,54 @@ public void rewriteBatchedStatementsInsertTest() throws SQLException { assertEquals(cycles, totalUpdates); } + /** + * CONJ-142: Using a semicolon in a string with "rewriteBatchedStatements=true" fails + * @throws SQLException + */ + @Test + public void rewriteBatchedStatementsSemicolon() throws SQLException { + // set the rewrite batch statements parameter + Properties props = new Properties(); + props.setProperty("rewriteBatchedStatements", "true"); + setConnection(props); + + connection.createStatement().execute("TRUNCATE t3"); + + PreparedStatement sqlInsert = connection.prepareStatement("INSERT INTO t3 (message) VALUES (?)"); + sqlInsert.setString(1, "aa"); + sqlInsert.addBatch(); + sqlInsert.setString(1, "b;b"); + sqlInsert.addBatch(); + sqlInsert.setString(1, ";ccccccc"); + sqlInsert.addBatch(); + sqlInsert.setString(1, "ddddddddddddddd;"); + sqlInsert.addBatch(); + sqlInsert.setString(1, ";eeeeeee;;eeeeeeeeee;eeeeeeeeee;"); + sqlInsert.addBatch(); + int[] updateCounts = sqlInsert.executeBatch(); + + // rewrite should be ok, so the above should be executed in 1 command updating 5 rows + Assert.assertEquals(1, updateCounts.length); + Assert.assertEquals(5, updateCounts[0]); + + connection.commit(); + + // Test for multiple statements which isn't allowed. rewrite shouldn't work + sqlInsert = connection.prepareStatement("INSERT INTO t3 (message) VALUES (?); INSERT INTO t3 (message) VALUES ('multiple')"); + sqlInsert.setString(1, "aa"); + sqlInsert.addBatch(); + sqlInsert.setString(1, "b;b"); + sqlInsert.addBatch(); + updateCounts = sqlInsert.executeBatch(); + + // rewrite should NOT be possible. Therefore there should be 2 commands updating 1 row each. + Assert.assertEquals(2, updateCounts.length); + Assert.assertEquals(1, updateCounts[0]); + Assert.assertEquals(1, updateCounts[1]); + + connection.commit(); + } + private PreparedStatement prepareStatementBatch(int size) throws SQLException { PreparedStatement preparedStatement = connection.prepareStatement("INSERT INTO t1 VALUES (?, ?)"); for (int i = 0; i < size; i++) { @@ -192,6 +247,11 @@ private PreparedStatement prepareStatementBatch(int size) throws SQLException { */ @Test public void rewriteBatchedStatementsUpdateTest() throws SQLException { + // set the rewrite batch statements parameter + Properties props = new Properties(); + props.setProperty("rewriteBatchedStatements", "true"); + connection.setClientInfo(props); + connection.createStatement().execute("TRUNCATE t1"); int cycles = 1000; prepareStatementBatch(cycles).executeBatch(); // populate the table