Skip to content

Commit d870e13

Browse files
committed
[CONJ-1274] have server prepared statement client failover
1 parent b43a845 commit d870e13

File tree

11 files changed

+172
-62
lines changed

11 files changed

+172
-62
lines changed

src/main/java/org/mariadb/jdbc/Connection.java

Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -162,18 +162,14 @@ public PreparedStatement prepareInternal(
162162
throws SQLException {
163163
checkNotClosed();
164164
if (useBinary && !sql.startsWith("/*client prepare*/")) {
165-
try {
166-
return new ServerPreparedStatement(
167-
NativeSql.parse(sql, client.getContext()),
168-
this,
169-
lock,
170-
autoGeneratedKeys,
171-
resultSetType,
172-
resultSetConcurrency,
173-
defaultFetchSize);
174-
} catch (SQLException e) {
175-
// failover to the client-side prepared statement
176-
}
165+
return new ServerPreparedStatement(
166+
NativeSql.parse(sql, client.getContext()),
167+
this,
168+
lock,
169+
autoGeneratedKeys,
170+
resultSetType,
171+
resultSetConcurrency,
172+
defaultFetchSize);
177173
}
178174
return new ClientPreparedStatement(
179175
NativeSql.parse(sql, client.getContext()),
@@ -717,7 +713,7 @@ public boolean isValid(int timeout) throws SQLException {
717713
if (initialSocketTimeout != timeout * 1000) {
718714
try {
719715
client.setSocketTimeout(initialSocketTimeout);
720-
} catch (SQLException sqle) {
716+
} catch (SQLException sqle) {
721717
// eat
722718
}
723719
}

src/main/java/org/mariadb/jdbc/ServerPreparedStatement.java

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,14 @@
77

88
import java.sql.*;
99
import java.util.*;
10-
import java.util.regex.Pattern;
1110
import org.mariadb.jdbc.client.ColumnDecoder;
1211
import org.mariadb.jdbc.client.Completion;
1312
import org.mariadb.jdbc.client.result.CompleteResult;
1413
import org.mariadb.jdbc.client.result.Result;
1514
import org.mariadb.jdbc.client.util.ClosableLock;
1615
import org.mariadb.jdbc.client.util.Parameters;
1716
import org.mariadb.jdbc.export.ExceptionFactory;
17+
import org.mariadb.jdbc.export.SQLPrepareException;
1818
import org.mariadb.jdbc.message.ClientMessage;
1919
import org.mariadb.jdbc.message.client.ExecutePacket;
2020
import org.mariadb.jdbc.message.client.PrepareExecutePacket;
@@ -29,8 +29,6 @@
2929
* COM_STMT_CLOSE)
3030
*/
3131
public class ServerPreparedStatement extends BasePreparedStatement {
32-
private static final Pattern PREPARABLE_STATEMENT_PATTERN =
33-
Pattern.compile("^(SELECT|UPDATE|INSERT|DELETE|REPLACE|DO|CALL)", Pattern.CASE_INSENSITIVE);
3432

3533
/**
3634
* Server prepare statement constructor
@@ -55,10 +53,6 @@ public ServerPreparedStatement(
5553
int defaultFetchSize)
5654
throws SQLException {
5755
super(sql, con, lock, autoGeneratedKeys, resultSetType, resultSetConcurrency, defaultFetchSize);
58-
prepareResult = con.cachePrepStmts() ? con.getContext().getPrepareCacheCmd(sql, this) : null;
59-
if (prepareResult == null && !PREPARABLE_STATEMENT_PATTERN.matcher(sql).find()) {
60-
con.getClient().execute(new PreparePacket(sql), this, true);
61-
}
6256
parameters = new ParameterList();
6357
}
6458

@@ -71,6 +65,13 @@ public ServerPreparedStatement(
7165
protected void executeInternal() throws SQLException {
7266
checkNotClosed();
7367
validParameters();
68+
69+
// in case parameter is > 65K, use client side prepared statement
70+
if (parameters.size() > 65 * 1024) {
71+
clientFailover();
72+
return;
73+
}
74+
7475
try (ClosableLock ignore = lock.closeableLock();
7576
QueryTimeoutHandler ignore2 = this.con.handleTimeout(queryTimeout)) {
7677
String cmd = escapeTimeout(sql);
@@ -81,6 +82,28 @@ protected void executeInternal() throws SQLException {
8182
} else {
8283
executeStandard(cmd);
8384
}
85+
} catch (SQLPrepareException prepareException) {
86+
clientFailover();
87+
} catch (SQLException e) {
88+
results = null;
89+
currResult = null;
90+
throw e;
91+
} finally {
92+
localInfileInputStream = null;
93+
}
94+
}
95+
96+
private void clientFailover() throws SQLException {
97+
ClientPreparedStatement clientPreparedStatement =
98+
new ClientPreparedStatement(
99+
sql, con, lock, autoGeneratedKeys, resultSetType, resultSetConcurrency, fetchSize);
100+
clientPreparedStatement.parameters = parameters;
101+
clientPreparedStatement.localInfileInputStream = localInfileInputStream;
102+
try {
103+
clientPreparedStatement.execute();
104+
results = clientPreparedStatement.results;
105+
results.add(0, clientPreparedStatement.currResult);
106+
currResult = null;
84107
} catch (SQLException e) {
85108
results = null;
86109
currResult = null;

src/main/java/org/mariadb/jdbc/client/impl/MultiPrimaryClient.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -239,7 +239,10 @@ protected void replayIfPossible(Client oldClient, boolean canRedo) throws SQLExc
239239
} else {
240240
// transaction is lost, but connection is now up again.
241241
// changing exception to SQLTransientConnectionException
242-
oldClient.getContext().setServerStatus(oldClient.getContext().getServerStatus() & ~ServerStatus.IN_TRANSACTION);
242+
oldClient
243+
.getContext()
244+
.setServerStatus(
245+
oldClient.getContext().getServerStatus() & ~ServerStatus.IN_TRANSACTION);
243246
throw new SQLTransientConnectionException(
244247
String.format(
245248
"Driver has reconnect connection after a communications link failure with %s. In"

src/main/java/org/mariadb/jdbc/export/ExceptionFactory.java

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -255,7 +255,7 @@ public ExceptionFactory withSql(String sql) {
255255
}
256256

257257
private SQLException createException(
258-
String initialMessage, String sqlState, int errorCode, Exception cause) {
258+
String initialMessage, String sqlState, int errorCode, Exception cause, boolean isPrepare) {
259259

260260
String msg = buildMsgText(initialMessage, threadId, conf, getSql(), errorCode, connection);
261261

@@ -286,7 +286,11 @@ private SQLException createException(
286286
case "20":
287287
case "42":
288288
case "XA":
289-
returnEx = new SQLSyntaxErrorException(msg, sqlState, errorCode, cause);
289+
if (isPrepare) {
290+
returnEx = new SQLPrepareException(msg, sqlState, errorCode, cause);
291+
} else {
292+
returnEx = new SQLSyntaxErrorException(msg, sqlState, errorCode, cause);
293+
}
290294
break;
291295
case "25":
292296
case "28":
@@ -303,7 +307,11 @@ private SQLException createException(
303307
returnEx = new SQLTransactionRollbackException(msg, sqlState, errorCode, cause);
304308
break;
305309
case "HY":
306-
returnEx = new SQLException(msg, sqlState, errorCode, cause);
310+
if (isPrepare) {
311+
returnEx = new SQLPrepareException(msg, sqlState, errorCode, cause);
312+
} else {
313+
returnEx = new SQLException(msg, sqlState, errorCode, cause);
314+
}
307315
break;
308316
default:
309317
returnEx = new SQLTransientConnectionException(msg, sqlState, errorCode, cause);
@@ -330,7 +338,7 @@ private SQLException createException(
330338
* @return exception to be thrown
331339
*/
332340
public SQLException notSupported(String message) {
333-
return createException(message, "0A000", -1, null);
341+
return createException(message, "0A000", -1, null, false);
334342
}
335343

336344
/**
@@ -340,7 +348,7 @@ public SQLException notSupported(String message) {
340348
* @return exception to be thrown
341349
*/
342350
public SQLException create(String message) {
343-
return createException(message, "42000", -1, null);
351+
return createException(message, "42000", -1, null, false);
344352
}
345353

346354
/**
@@ -351,7 +359,7 @@ public SQLException create(String message) {
351359
* @return exception to be thrown
352360
*/
353361
public SQLException create(String message, String sqlState) {
354-
return createException(message, sqlState, -1, null);
362+
return createException(message, sqlState, -1, null, false);
355363
}
356364

357365
/**
@@ -363,7 +371,7 @@ public SQLException create(String message, String sqlState) {
363371
* @return exception to be thrown
364372
*/
365373
public SQLException create(String message, String sqlState, Exception cause) {
366-
return createException(message, sqlState, -1, cause);
374+
return createException(message, sqlState, -1, cause, false);
367375
}
368376

369377
/**
@@ -375,7 +383,19 @@ public SQLException create(String message, String sqlState, Exception cause) {
375383
* @return exception to be thrown
376384
*/
377385
public SQLException create(String message, String sqlState, int errorCode) {
378-
return createException(message, sqlState, errorCode, null);
386+
return createException(message, sqlState, errorCode, null, false);
387+
}
388+
389+
/**
390+
* Creation of an exception
391+
*
392+
* @param message error message
393+
* @param sqlState sql state
394+
* @param errorCode error code
395+
* @return exception to be thrown
396+
*/
397+
public SQLException create(String message, String sqlState, int errorCode, boolean isPrepare) {
398+
return createException(message, sqlState, errorCode, null, isPrepare);
379399
}
380400

381401
/**
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// SPDX-License-Identifier: LGPL-2.1-or-later
2+
// Copyright (c) 2012-2014 Monty Program Ab
3+
// Copyright (c) 2015-2025 MariaDB Corporation Ab
4+
package org.mariadb.jdbc.export;
5+
6+
import java.sql.SQLException;
7+
8+
public class SQLPrepareException extends SQLException {
9+
public SQLPrepareException(String reason, String sqlState, int vendorCode, Throwable cause) {
10+
super(reason, sqlState, vendorCode, cause);
11+
}
12+
}

src/main/java/org/mariadb/jdbc/message/client/PrepareExecutePacket.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,10 @@ public Completion readPacket(
154154
throw exceptionFactory
155155
.withSql(this.description())
156156
.create(
157-
errorPacket.getMessage(), errorPacket.getSqlState(), errorPacket.getErrorCode());
157+
errorPacket.getMessage(),
158+
errorPacket.getSqlState(),
159+
errorPacket.getErrorCode(),
160+
true);
158161
}
159162
if (context.getConf().useServerPrepStmts()
160163
&& context.getConf().cachePrepStmts()

src/main/java/org/mariadb/jdbc/message/client/PreparePacket.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,11 @@ public Completion readPacket(
7272
ErrorPacket errorPacket = new ErrorPacket(buf, context);
7373
throw exceptionFactory
7474
.withSql(this.description())
75-
.create(errorPacket.getMessage(), errorPacket.getSqlState(), errorPacket.getErrorCode());
75+
.create(
76+
errorPacket.getMessage(),
77+
errorPacket.getSqlState(),
78+
errorPacket.getErrorCode(),
79+
true);
7680
}
7781
if (context.getConf().useServerPrepStmts()
7882
&& context.getConf().cachePrepStmts()

src/test/java/org/mariadb/jdbc/integration/ConnectionTest.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,6 @@ public void initSQL() throws SQLException {
124124
assertEquals("YourVar", rs.getString(1));
125125
assertEquals("YourVar2", rs.getString(2));
126126
}
127-
128127
}
129128

130129
@Test

src/test/java/org/mariadb/jdbc/integration/FailoverTest.java

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,8 @@ private void transactionReplayDuringCommit(boolean transactionReplay) throws SQL
104104
String tableName = "transaction_failover_" + (transactionReplay ? "1" : "2");
105105
st.execute("DROP TABLE IF EXISTS " + tableName);
106106
st.execute(
107-
"CREATE TABLE " + tableName
107+
"CREATE TABLE "
108+
+ tableName
108109
+ " (id int not null primary key auto_increment, test varchar(20)) "
109110
+ "engine=innodb");
110111

@@ -208,8 +209,8 @@ private void transactionReplayPreparedStatement(boolean binary, boolean transact
208209
public void transactionReplayPreparedStatementBatch() throws Exception {
209210
Assumptions.assumeTrue(!isMaxscale());
210211
for (int i = 0; i < 16; i++) {
211-
System.out.println("transactionReplayPreparedStatementBatch:" + i);
212-
transactionReplayPreparedStatementBatch(i, (i & 1) > 0, (i & 2) > 0, (i & 4) > 0, (i & 8) > 0);
212+
transactionReplayPreparedStatementBatch(
213+
i, (i & 1) > 0, (i & 2) > 0, (i & 4) > 0, (i & 8) > 0);
213214
}
214215
}
215216

@@ -248,11 +249,14 @@ private void execute(int idx, Connection con, boolean transactionReplay, long th
248249
throws SQLException {
249250
Statement stmt = con.createStatement();
250251

251-
stmt.executeUpdate("INSERT INTO transaction_failover_batch_"+idx+" (test) VALUES ('test0')");
252+
stmt.executeUpdate(
253+
"INSERT INTO transaction_failover_batch_" + idx + " (test) VALUES ('test0')");
252254
con.setAutoCommit(false);
253-
stmt.executeUpdate("INSERT INTO transaction_failover_batch_"+idx+" (test) VALUES ('test1')");
255+
stmt.executeUpdate(
256+
"INSERT INTO transaction_failover_batch_" + idx + " (test) VALUES ('test1')");
254257
try (PreparedStatement p =
255-
con.prepareStatement("INSERT INTO transaction_failover_batch_"+idx+" (test) VALUES (?)")) {
258+
con.prepareStatement(
259+
"INSERT INTO transaction_failover_batch_" + idx + " (test) VALUES (?)")) {
256260
p.setString(1, "test2");
257261
p.execute();
258262
p.setString(1, "test3");
@@ -271,7 +275,7 @@ private void execute(int idx, Connection con, boolean transactionReplay, long th
271275
p.executeBatch();
272276
con.commit();
273277

274-
ResultSet rs = stmt.executeQuery("SELECT * FROM transaction_failover_batch_"+idx);
278+
ResultSet rs = stmt.executeQuery("SELECT * FROM transaction_failover_batch_" + idx);
275279
for (int i = 0; i < 6; i++) {
276280
assertTrue(rs.next());
277281
assertEquals("test" + i, rs.getString("test"));
@@ -293,12 +297,15 @@ private void execute(int idx, Connection con, boolean transactionReplay, long th
293297
}
294298
}
295299
}
296-
stmt.execute("TRUNCATE transaction_failover_batch_"+idx);
297-
stmt.executeUpdate("INSERT INTO transaction_failover_batch_"+idx+" (test) VALUES ('test0')");
300+
stmt.execute("TRUNCATE transaction_failover_batch_" + idx);
301+
stmt.executeUpdate(
302+
"INSERT INTO transaction_failover_batch_" + idx + " (test) VALUES ('test0')");
298303
con.setAutoCommit(false);
299-
stmt.executeUpdate("INSERT INTO transaction_failover_batch_"+idx+" (test) VALUES ('test1')");
304+
stmt.executeUpdate(
305+
"INSERT INTO transaction_failover_batch_" + idx + " (test) VALUES ('test1')");
300306
try (PreparedStatement p =
301-
con.prepareStatement("INSERT INTO transaction_failover_batch_"+idx+" (test) VALUES (?)")) {
307+
con.prepareStatement(
308+
"INSERT INTO transaction_failover_batch_" + idx + " (test) VALUES (?)")) {
302309

303310
proxy.restart(300);
304311
p.setString(1, "test2");
@@ -314,7 +321,7 @@ private void execute(int idx, Connection con, boolean transactionReplay, long th
314321
p.executeBatch();
315322
con.commit();
316323

317-
ResultSet rs = stmt.executeQuery("SELECT * FROM transaction_failover_batch_"+idx);
324+
ResultSet rs = stmt.executeQuery("SELECT * FROM transaction_failover_batch_" + idx);
318325
for (int i = 0; i < 5; i++) {
319326
assertTrue(rs.next());
320327
assertEquals("test" + i, rs.getString("test"));

src/test/java/org/mariadb/jdbc/integration/ParameterMetaDataTest.java

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -122,17 +122,10 @@ public void parameterMetaDataNotPreparable() throws Exception {
122122
}
123123
}
124124
try (org.mariadb.jdbc.Connection con = createCon("&useServerPrepStmts")) {
125-
// statement that cannot be prepared
125+
// statement that cannot be prepared must fail over to client side prepared statement
126126
try (PreparedStatement pstmt =
127-
con.prepareStatement("select TMP.field1 from (select ? from dual) TMP")) {
128-
try {
129-
pstmt.getParameterMetaData();
130-
fail();
131-
} catch (SQLSyntaxErrorException e) {
132-
// eat
133-
}
134-
} catch (SQLSyntaxErrorException e) {
135-
// eat
127+
con.prepareStatement("select TMP.field1 from (select ? as field1 from dual) TMP")) {
128+
pstmt.getParameterMetaData();
136129
}
137130
}
138131
}

0 commit comments

Comments
 (0)