/
MariaDbPreparedStatementClient.java
523 lines (470 loc) · 21.4 KB
/
MariaDbPreparedStatementClient.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
/*
*
* MariaDB Client for Java
*
* Copyright (c) 2012-2014 Monty Program Ab.
* Copyright (c) 2015-2017 MariaDB Ab.
*
* This library is free software; you can redistribute it and/or modify it under
* the terms of the GNU Lesser General Public License as published by the Free
* Software Foundation; either version 2.1 of the License, or (at your option)
* any later version.
*
* This library is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License along
* with this library; if not, write to Monty Program Ab info@montyprogram.com.
*
* This particular MariaDB Client for Java file is work
* derived from a Drizzle-JDBC. Drizzle-JDBC file which is covered by subject to
* the following copyright and notice provisions:
*
* Copyright (c) 2009-2011, Marcus Eriksson
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this list
* of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright notice, this
* list of conditions and the following disclaimer in the documentation and/or
* other materials provided with the distribution.
*
* Neither the name of the driver nor the names of its contributors may not be
* used to endorse or promote products derived from this software without specific
* prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
* OF SUCH DAMAGE.
*
*/
package org.mariadb.jdbc;
import org.mariadb.jdbc.internal.com.read.dao.Results;
import org.mariadb.jdbc.internal.com.read.resultset.SelectResultSet;
import org.mariadb.jdbc.internal.com.send.parameters.ParameterHolder;
import org.mariadb.jdbc.internal.logging.Logger;
import org.mariadb.jdbc.internal.logging.LoggerFactory;
import org.mariadb.jdbc.internal.util.dao.ClientPrepareResult;
import org.mariadb.jdbc.internal.util.exceptions.ExceptionMapper;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;
public class MariaDbPreparedStatementClient extends BasePrepareStatement {
private static final Logger logger = LoggerFactory.getLogger(MariaDbPreparedStatementClient.class);
private final List<ParameterHolder[]> parameterList = new ArrayList<>();
private ClientPrepareResult prepareResult;
private String sqlQuery;
private ParameterHolder[] parameters;
private ResultSetMetaData resultSetMetaData = null;
private ParameterMetaData parameterMetaData = null;
/**
* Constructor.
*
* @param connection connection
* @param sql sql query
* @param resultSetScrollType one of the following <code>ResultSet</code> constants: <code>ResultSet.TYPE_FORWARD_ONLY</code>,
* <code>ResultSet.TYPE_SCROLL_INSENSITIVE</code>, or <code>ResultSet.TYPE_SCROLL_SENSITIVE</code>
* @param resultSetConcurrency a concurrency type; one of <code>ResultSet.CONCUR_READ_ONLY</code> or
* <code>ResultSet.CONCUR_UPDATABLE</code>
* @param autoGeneratedKeys a flag indicating whether auto-generated keys should be returned; one of
* <code>Statement.RETURN_GENERATED_KEYS</code>
* or <code>Statement.NO_GENERATED_KEYS</code>
* @throws SQLException exception
*/
public MariaDbPreparedStatementClient(MariaDbConnection connection, String sql, int resultSetScrollType,
int resultSetConcurrency, int autoGeneratedKeys) throws SQLException {
super(connection, resultSetScrollType, resultSetConcurrency, autoGeneratedKeys);
sqlQuery = sql;
if (options.cachePrepStmts) {
String key = protocol.getDatabase() + "-" + sqlQuery;
prepareResult = connection.getClientPrepareStatementCache().get(key);
}
if (prepareResult == null) {
if (options.rewriteBatchedStatements) {
prepareResult = ClientPrepareResult.rewritableParts(sqlQuery, protocol.noBackslashEscapes());
} else {
prepareResult = ClientPrepareResult.parameterParts(sqlQuery, protocol.noBackslashEscapes());
}
if (options.cachePrepStmts && sql.length() < 1024) {
String key = protocol.getDatabase() + "-" + sqlQuery;
connection.getClientPrepareStatementCache().put(key, prepareResult);
}
}
parameters = new ParameterHolder[prepareResult.getParamCount()];
}
/**
* Clone statement.
*
* @param connection connection
* @return Clone statement.
* @throws CloneNotSupportedException if any error occur.
*/
public MariaDbPreparedStatementClient clone(MariaDbConnection connection) throws CloneNotSupportedException {
MariaDbPreparedStatementClient clone = (MariaDbPreparedStatementClient) super.clone(connection);
clone.sqlQuery = sqlQuery;
clone.prepareResult = prepareResult;
clone.parameters = new ParameterHolder[prepareResult.getParamCount()];
clone.resultSetMetaData = resultSetMetaData;
clone.parameterMetaData = parameterMetaData;
return clone;
}
/**
* Executes the SQL statement in this <code>PreparedStatement</code> object,
* which may be any kind of SQL statement.
* Some prepared statements return multiple results; the <code>execute</code>
* method handles these complex statements as well as the simpler
* form of statements handled by the methods <code>executeQuery</code>
* and <code>executeUpdate</code>.
* <br>
* The <code>execute</code> method returns a <code>boolean</code> to
* indicate the form of the first result. You must call either the method
* <code>getResultSet</code> or <code>getUpdateCount</code>
* to retrieve the result; you must call <code>getInternalMoreResults</code> to
* move to any subsequent result(s).
*
* @return <code>true</code> if the first result is a <code>ResultSet</code>
* object; <code>false</code> if the first result is an update
* count or there is no result
* @throws SQLException if a database access error occurs;
* this method is called on a closed <code>PreparedStatement</code>
* or an argument is supplied to this method
* @see Statement#execute
* @see Statement#getResultSet
* @see Statement#getUpdateCount
* @see Statement#getMoreResults
*/
public boolean execute() throws SQLException {
return executeInternal(getFetchSize());
}
/**
* Executes the SQL query in this <code>PreparedStatement</code> object
* and returns the <code>ResultSet</code> object generated by the query.
*
* @return a <code>ResultSet</code> object that contains the data produced by the
* query; never <code>null</code>
* @throws SQLException if a database access error occurs;
* this method is called on a closed <code>PreparedStatement</code> or the SQL
* statement does not return a <code>ResultSet</code> object
*/
public ResultSet executeQuery() throws SQLException {
if (execute()) {
return results.getResultSet();
}
return SelectResultSet.createEmptyResultSet();
}
/**
* Executes the SQL statement in this <code>PreparedStatement</code> object, which must be an SQL Data Manipulation
* Language (DML) statement, such as <code>INSERT</code>, <code>UPDATE</code> or <code>DELETE</code>; or an SQL
* statement that returns nothing, such as a DDL statement.
*
* @return either (1) the row count for SQL Data Manipulation Language (DML) statements or (2) 0 for SQL statements
* that return nothing
* @throws SQLException if a database access error occurs; this method is called on a closed
* <code>PreparedStatement</code> or the SQL statement returns a
* <code>ResultSet</code> object
*/
public int executeUpdate() throws SQLException {
if (execute()) {
return 0;
}
return getUpdateCount();
}
protected boolean executeInternal(int fetchSize) throws SQLException {
//valid parameters
for (int i = 0; i < prepareResult.getParamCount(); i++) {
if (parameters[i] == null) {
logger.error("Parameter at position {} is not set", (i + 1));
ExceptionMapper.throwException(new SQLException("Parameter at position " + (i + 1) + " is not set", "07004"),
connection, this);
}
}
lock.lock();
try {
executeQueryPrologue(false);
results = new Results(this, fetchSize, false, 1, false, resultSetScrollType, resultSetConcurrency, autoGeneratedKeys,
protocol.getAutoIncrementIncrement());
if (queryTimeout != 0 && canUseServerTimeout) {
//timer will not be used for timeout to avoid having threads
protocol.executeQuery(protocol.isMasterConnection(), results, prepareResult, parameters, queryTimeout);
} else {
protocol.executeQuery(protocol.isMasterConnection(), results, prepareResult, parameters);
}
results.commandEnd();
return results.getResultSet() != null;
} catch (SQLException exception) {
throw executeExceptionEpilogue(exception);
} finally {
executeEpilogue();
lock.unlock();
}
}
/**
* Adds a set of parameters to this <code>PreparedStatement</code> object's batch of send.
* <br>
* <br>
*
* @throws SQLException if a database access error occurs or this method is called on a closed
* <code>PreparedStatement</code>
* @see Statement#addBatch
* @since 1.2
*/
public void addBatch() throws SQLException {
ParameterHolder[] holder = new ParameterHolder[prepareResult.getParamCount()];
for (int i = 0; i < holder.length; i++) {
holder[i] = parameters[i];
if (holder[i] == null) {
logger.error("You need to set exactly " + prepareResult.getParamCount()
+ " parameters on the prepared statement");
throw ExceptionMapper.getSqlException("You need to set exactly " + prepareResult.getParamCount()
+ " parameters on the prepared statement");
}
}
parameterList.add(holder);
}
/**
* Add batch.
*
* @param sql typically this is a SQL <code>INSERT</code> or <code>UPDATE</code> statement
* @throws SQLException every time since that method is forbidden on prepareStatement
*/
@Override
public void addBatch(final String sql) throws SQLException {
throw new SQLException("Cannot do addBatch(String) on preparedStatement");
}
/**
* Clear batch.
*/
@Override
public void clearBatch() {
parameterList.clear();
hasLongData = false;
this.parameters = new ParameterHolder[prepareResult.getParamCount()];
}
/**
* {inheritdoc}.
*/
public int[] executeBatch() throws SQLException {
checkClose();
int size = parameterList.size();
if (size == 0) return new int[0];
lock.lock();
try {
executeInternalBatch(size);
results.commandEnd();
return results.getCmdInformation().getUpdateCounts();
} catch (SQLException sqle) {
results.commandEnd();
throw executeBatchExceptionEpilogue(sqle, results.getCmdInformation(), size);
} finally {
executeBatchEpilogue();
lock.unlock();
}
}
/**
* Execute batch, like executeBatch(), with returning results with long[].
* For when row count may exceed Integer.MAX_VALUE.
*
* @return an array of update counts (one element for each command in the batch)
* @throws SQLException if a database error occur.
*/
public long[] executeLargeBatch() throws SQLException {
checkClose();
int size = parameterList.size();
if (size == 0) return new long[0];
lock.lock();
try {
executeInternalBatch(size);
results.commandEnd();
return results.getCmdInformation().getLargeUpdateCounts();
} catch (SQLException sqle) {
results.commandEnd();
throw executeBatchExceptionEpilogue(sqle, results.getCmdInformation(), size);
} finally {
executeBatchEpilogue();
lock.unlock();
}
}
/**
* Choose better way to execute queries according to query and options.
*
* @param size parameters number
* @throws SQLException if any error occur
*/
private void executeInternalBatch(int size) throws SQLException {
executeQueryPrologue(true);
results.reset(0, true, size, false, resultSetScrollType, resultSetConcurrency, autoGeneratedKeys);
if (protocol.executeBatchClient(protocol.isMasterConnection(), results, prepareResult, parameterList, hasLongData)) return;
//send query one by one, reading results for each query before sending another one
SQLException exception = null;
if (queryTimeout > 0) {
for (int batchQueriesCount = 0; batchQueriesCount < size; batchQueriesCount++) {
protocol.stopIfInterrupted();
try {
protocol.executeQuery(protocol.isMasterConnection(), results, prepareResult, parameterList.get(batchQueriesCount));
} catch (SQLException e) {
if (options.continueBatchOnError) {
exception = e;
} else {
throw e;
}
}
}
} else {
for (int batchQueriesCount = 0; batchQueriesCount < size; batchQueriesCount++) {
try {
protocol.executeQuery(protocol.isMasterConnection(), results, prepareResult, parameterList.get(batchQueriesCount));
} catch (SQLException e) {
if (options.continueBatchOnError) {
exception = e;
} else {
throw e;
}
}
}
}
if (exception != null) throw exception;
}
/**
* Retrieves a <code>ResultSetMetaData</code> object that contains information about the columns of the
* <code>ResultSet</code> object that will be returned when this <code>PreparedStatement</code> object is executed.
* <br>
* Because a <code>PreparedStatement</code> object is precompiled, it is possible to know about the
* <code>ResultSet</code> object that it will return without having to execute it. Consequently, it is possible to
* invoke the method <code>getMetaData</code> on a <code>PreparedStatement</code> object rather than waiting to
* execute it and then invoking the <code>ResultSet.getMetaData</code> method on the <code>ResultSet</code> object
* that is returned.
* @return the description of a <code>ResultSet</code> object's columns or <code>null</code> if the driver cannot
* return a <code>ResultSetMetaData</code> object
* @throws SQLException if a database access error occurs or this method is called on a closed
* <code>PreparedStatement</code>
* @throws SQLFeatureNotSupportedException if the JDBC driver does not support this method
*/
public ResultSetMetaData getMetaData() throws SQLException {
checkClose();
ResultSet rs = getResultSet();
if (rs != null) {
return rs.getMetaData();
}
if (resultSetMetaData == null) {
loadParametersData();
}
return resultSetMetaData;
}
/**
* Set parameter.
*
* @param parameterIndex index
* @param holder parameter holder
* @throws SQLException if index position doesn't correspond to query parameters
*/
public void setParameter(final int parameterIndex, final ParameterHolder holder) throws SQLException {
if (parameterIndex >= 1 && parameterIndex < prepareResult.getParamCount() + 1) {
parameters[parameterIndex - 1] = holder;
} else {
String error = "Could not set parameter at position " + parameterIndex
+ " (values was " + holder.toString() + ")\n"
+ "Query - conn:" + protocol.getServerThreadId()
+ "(" + (protocol.isMasterConnection() ? "M" : "S") + ") ";
if (options.maxQuerySizeToLog > 0) {
error += " - \"";
if (sqlQuery.length() < options.maxQuerySizeToLog) {
error += sqlQuery;
} else {
error += sqlQuery.substring(0, options.maxQuerySizeToLog) + "...";
}
error += "\"";
} else {
error += " - \"" + sqlQuery + "\"";
}
logger.error(error);
throw ExceptionMapper.getSqlException(error);
}
}
/**
* Retrieves the number, types and properties of this <code>PreparedStatement</code> object's parameters.
*
* @return a <code>ParameterMetaData</code> object that contains information about the number, types and properties
* for each parameter marker of this <code>PreparedStatement</code> object
* @throws SQLException if a database access error occurs or this method is called on a closed
* <code>PreparedStatement</code>
* @see ParameterMetaData
* @since 1.4
*/
public ParameterMetaData getParameterMetaData() throws SQLException {
checkClose();
if (parameterMetaData == null) {
loadParametersData();
}
return parameterMetaData;
}
private void loadParametersData() throws SQLSyntaxErrorException {
try (MariaDbPreparedStatementServer ssps = new MariaDbPreparedStatementServer(connection, sqlQuery,
ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY, Statement.NO_GENERATED_KEYS)) {
resultSetMetaData = ssps.getMetaData();
parameterMetaData = ssps.getParameterMetaData();
} catch (SQLSyntaxErrorException sqlSyntaxErrorException) {
//if error is due to wrong SQL syntax, better to throw exception immediately
throw sqlSyntaxErrorException;
} catch (SQLException sqle) {
parameterMetaData = new MariaDbParameterMetaData(null);
}
}
/**
* Clears the current parameter values immediately. <P>In general, parameter values remain in force for repeated use
* of a statement. Setting a parameter value automatically clears its previous value. However, in some cases it is
* useful to immediately release the resources used by the current parameter values; this can be done by calling the
* method <code>clearParameters</code>.
*/
public void clearParameters() {
parameters = new ParameterHolder[prepareResult.getParamCount()];
}
// Close prepared statement, maybe fire closed-statement events
@Override
public void close() throws SQLException {
super.close();
if (connection == null || connection.pooledConnection == null
|| connection.pooledConnection.noStmtEventListeners()) {
return;
}
connection.pooledConnection.fireStatementClosed(this);
connection = null;
}
protected int getParameterCount() {
return prepareResult.getParamCount();
}
/**
* {inherit}.
*/
@Override
public String toString() {
StringBuilder sb = new StringBuilder("sql : '" + sqlQuery + "'");
sb.append(", parameters : [");
for (int i = 0; i < parameters.length; i++) {
ParameterHolder holder = parameters[i];
if (holder == null) {
sb.append("null");
} else {
sb.append(holder.toString());
}
if (i != parameters.length - 1) {
sb.append(",");
}
}
sb.append("]");
return sb.toString();
}
protected ClientPrepareResult getPrepareResult() {
return prepareResult;
}
}