Skip to content

Commit 5bf304e

Browse files
committed
disabled calling some Statement method from PreparedStatement.
1 parent a245c1f commit 5bf304e

File tree

4 files changed

+186
-28
lines changed

4 files changed

+186
-28
lines changed

jdbc-v2/src/main/java/com/clickhouse/jdbc/PreparedStatementImpl.java

Lines changed: 134 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.clickhouse.jdbc;
22

33
import com.clickhouse.client.api.metadata.TableSchema;
4+
import com.clickhouse.client.api.query.QuerySettings;
45
import com.clickhouse.data.Tuple;
56
import com.clickhouse.jdbc.internal.ExceptionUtils;
67
import com.clickhouse.jdbc.internal.JdbcUtils;
@@ -107,13 +108,14 @@ private String compileSql(String []segments) {
107108
@Override
108109
public ResultSet executeQuery() throws SQLException {
109110
checkClosed();
110-
return executeQuery(compileSql(sqlSegments));
111+
return super.executeQueryImpl(compileSql(sqlSegments), new QuerySettings().setDatabase(connection.getSchema()));
111112
}
112113

113114
@Override
114115
public int executeUpdate() throws SQLException {
115116
checkClosed();
116-
return executeUpdate(compileSql(sqlSegments));
117+
return super.executeUpdateImpl(compileSql(sqlSegments), statementType,
118+
new QuerySettings().setDatabase(connection.getSchema()));
117119
}
118120

119121
@Override
@@ -240,17 +242,18 @@ public void setObject(int parameterIndex, Object x) throws SQLException {
240242
@Override
241243
public boolean execute() throws SQLException {
242244
checkClosed();
243-
return execute(compileSql(sqlSegments));
245+
return super.executeImpl(compileSql(sqlSegments), statementType,
246+
new QuerySettings().setDatabase(connection.getSchema()));
244247
}
245248

246249
@Override
247250
public void addBatch() throws SQLException {
248251
checkClosed();
249252
if (statementType == StatementType.INSERT) {
250253
// adding values to the end of big INSERT statement.
251-
addBatch(compileSql(valueSegments));
254+
super.addBatch(compileSql(valueSegments));
252255
} else {
253-
addBatch(compileSql(sqlSegments));
256+
super.addBatch(compileSql(sqlSegments));
254257
}
255258
}
256259

@@ -265,7 +268,8 @@ public int[] executeBatch() throws SQLException {
265268
sb.append(sql).append(",");
266269
}
267270
sb.setCharAt(sb.length() - 1, ';');
268-
int rowsInserted = executeUpdate(sb.toString());
271+
int rowsInserted = executeUpdateImpl(sb.toString(), statementType,
272+
new QuerySettings().setDatabase(connection.getSchema()));
269273
// clear batch and re-add insert into
270274
int[] results = new int[batch.size()];
271275
if (rowsInserted == batch.size()) {
@@ -280,18 +284,22 @@ public int[] executeBatch() throws SQLException {
280284
return results;
281285
} else {
282286
// run executeBatch
283-
return super.executeBatch();
287+
return executeBatchImpl().stream().mapToInt(Integer::intValue).toArray();
284288
}
285289
}
286290

287291
@Override
288292
public long[] executeLargeBatch() throws SQLException {
289-
int[] results = executeBatch();
290-
long[] longResults = new long[results.length];
291-
for (int i = 0; i < results.length; i++) {
292-
longResults[i] = results[i];
293+
return executeBatchImpl().stream().mapToLong(Integer::longValue).toArray();
294+
}
295+
296+
private List<Integer> executeBatchImpl() throws SQLException {
297+
List<Integer> results = new ArrayList<>();
298+
QuerySettings settings = new QuerySettings().setDatabase(connection.getSchema());
299+
for (String sql : batch) {
300+
results.add(executeUpdateImpl(sql, statementType, settings));
293301
}
294-
return longResults;
302+
return results;
295303
}
296304

297305
@Override
@@ -418,7 +426,8 @@ public ParameterMetaData getParameterMetaData() throws SQLException {
418426
@Override
419427
public void setRowId(int parameterIndex, RowId x) throws SQLException {
420428
checkClosed();
421-
parameters[parameterIndex - 1] = encodeObject(x);
429+
throw new SQLException("ROWID type is not supported by ClickHouse.",
430+
ExceptionUtils.SQL_STATE_FEATURE_NOT_SUPPORTED);
422431
}
423432

424433
@Override
@@ -546,6 +555,118 @@ public long executeLargeUpdate() throws SQLException {
546555
return executeUpdate();
547556
}
548557

558+
@Override
559+
public final void addBatch(String sql) throws SQLException {
560+
checkClosed();
561+
throw new SQLException(
562+
"addBatch(String) cannot be called in PreparedStatement or CallableStatement!",
563+
ExceptionUtils.SQL_STATE_WRONG_OBJECT_TYPE);
564+
}
565+
566+
@Override
567+
public final boolean execute(String sql) throws SQLException {
568+
checkClosed();
569+
throw new SQLException(
570+
"execute(String) cannot be called in PreparedStatement or CallableStatement!",
571+
ExceptionUtils.SQL_STATE_WRONG_OBJECT_TYPE);
572+
}
573+
574+
@Override
575+
public final boolean execute(String sql, int autoGeneratedKeys) throws SQLException {
576+
checkClosed();
577+
throw new SQLException(
578+
"execute(String, int) cannot be called in PreparedStatement or CallableStatement!",
579+
ExceptionUtils.SQL_STATE_WRONG_OBJECT_TYPE);
580+
}
581+
582+
@Override
583+
public final boolean execute(String sql, int[] columnIndexes) throws SQLException {
584+
checkClosed();
585+
throw new SQLException(
586+
"execute(String, int[]) cannot be called in PreparedStatement or CallableStatement!",
587+
ExceptionUtils.SQL_STATE_WRONG_OBJECT_TYPE);
588+
}
589+
590+
@Override
591+
public final boolean execute(String sql, String[] columnNames) throws SQLException {
592+
checkClosed();
593+
throw new SQLException(
594+
"execute(String, String[]) cannot be called in PreparedStatement or CallableStatement!",
595+
ExceptionUtils.SQL_STATE_WRONG_OBJECT_TYPE);
596+
}
597+
598+
@Override
599+
public final long executeLargeUpdate(String sql) throws SQLException {
600+
checkClosed();
601+
throw new SQLException(
602+
"executeLargeUpdate(String) cannot be called in PreparedStatement or CallableStatement!",
603+
ExceptionUtils.SQL_STATE_WRONG_OBJECT_TYPE);
604+
}
605+
606+
@Override
607+
public final long executeLargeUpdate(String sql, int autoGeneratedKeys) throws SQLException {
608+
checkClosed();
609+
throw new SQLException(
610+
"executeLargeUpdate(String, int) cannot be called in PreparedStatement or CallableStatement!",
611+
ExceptionUtils.SQL_STATE_WRONG_OBJECT_TYPE);
612+
}
613+
614+
@Override
615+
public final long executeLargeUpdate(String sql, int[] columnIndexes) throws SQLException {
616+
checkClosed();
617+
throw new SQLException(
618+
"executeLargeUpdate(String, int[]) cannot be called in PreparedStatement or CallableStatement!",
619+
ExceptionUtils.SQL_STATE_WRONG_OBJECT_TYPE);
620+
}
621+
622+
@Override
623+
public final long executeLargeUpdate(String sql, String[] columnNames) throws SQLException {
624+
checkClosed();
625+
throw new SQLException(
626+
"executeLargeUpdate(String, String[]) cannot be called in PreparedStatement or CallableStatement!",
627+
ExceptionUtils.SQL_STATE_WRONG_OBJECT_TYPE);
628+
}
629+
630+
@Override
631+
public final ResultSet executeQuery(String sql) throws SQLException {
632+
checkClosed();
633+
throw new SQLException(
634+
"executeQuery(String) cannot be called in PreparedStatement or CallableStatement!",
635+
ExceptionUtils.SQL_STATE_WRONG_OBJECT_TYPE);
636+
}
637+
638+
@Override
639+
public final int executeUpdate(String sql) throws SQLException {
640+
checkClosed();
641+
throw new SQLException(
642+
"executeUpdate(String) cannot be called in PreparedStatement or CallableStatement!",
643+
ExceptionUtils.SQL_STATE_WRONG_OBJECT_TYPE);
644+
}
645+
646+
@Override
647+
public final int executeUpdate(String sql, int autoGeneratedKeys) throws SQLException {
648+
checkClosed();
649+
throw new SQLException(
650+
"executeUpdate(String, int) cannot be called in PreparedStatement or CallableStatement!",
651+
ExceptionUtils.SQL_STATE_WRONG_OBJECT_TYPE);
652+
}
653+
654+
@Override
655+
public final int executeUpdate(String sql, int[] columnIndexes) throws SQLException {
656+
checkClosed();
657+
throw new SQLException(
658+
"executeUpdate(String, int[]) cannot be called in PreparedStatement or CallableStatement!",
659+
ExceptionUtils.SQL_STATE_WRONG_OBJECT_TYPE);
660+
}
661+
662+
@Override
663+
public final int executeUpdate(String sql, String[] columnNames) throws SQLException {
664+
checkClosed();
665+
throw new SQLException(
666+
"executeUpdate(String, String[]) cannot be called in PreparedStatement or CallableStatement!",
667+
ExceptionUtils.SQL_STATE_WRONG_OBJECT_TYPE);
668+
}
669+
549670
private static String encodeObject(Object x) throws SQLException {
550671
LOG.trace("Encoding object: {}", x);
551672

jdbc-v2/src/main/java/com/clickhouse/jdbc/StatementImpl.java

Lines changed: 12 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ protected void checkClosed() throws SQLException {
5656
}
5757
}
5858

59-
protected enum StatementType {
59+
public enum StatementType {
6060
SELECT, INSERT, DELETE, UPDATE, CREATE, DROP, ALTER, TRUNCATE, USE, SHOW, DESCRIBE, EXPLAIN, SET, KILL, OTHER, INSERT_INTO_SELECT
6161
}
6262

@@ -148,7 +148,7 @@ protected String getLastSql() {
148148
@Override
149149
public ResultSet executeQuery(String sql) throws SQLException {
150150
checkClosed();
151-
return executeQuery(sql, new QuerySettings().setDatabase(schema));
151+
return executeQueryImpl(sql, new QuerySettings().setDatabase(schema));
152152
}
153153

154154
private void closePreviousResultSet() {
@@ -165,7 +165,7 @@ private void closePreviousResultSet() {
165165
}
166166
}
167167

168-
public ResultSetImpl executeQuery(String sql, QuerySettings settings) throws SQLException {
168+
public ResultSetImpl executeQueryImpl(String sql, QuerySettings settings) throws SQLException {
169169
checkClosed();
170170
// Closing before trying to do next request. Otherwise, deadlock because previous connection will not be
171171
// release before this one completes.
@@ -213,13 +213,12 @@ public ResultSetImpl executeQuery(String sql, QuerySettings settings) throws SQL
213213
@Override
214214
public int executeUpdate(String sql) throws SQLException {
215215
checkClosed();
216-
return executeUpdate(sql, new QuerySettings().setDatabase(schema));
216+
return executeUpdateImpl(sql, parseStatementType(sql), new QuerySettings().setDatabase(schema));
217217
}
218218

219-
public int executeUpdate(String sql, QuerySettings settings) throws SQLException {
220-
// TODO: close current result set?
219+
protected int executeUpdateImpl(String sql, StatementType type, QuerySettings settings) throws SQLException {
221220
checkClosed();
222-
StatementType type = parseStatementType(sql);
221+
223222
if (type == StatementType.SELECT || type == StatementType.SHOW || type == StatementType.DESCRIBE || type == StatementType.EXPLAIN) {
224223
throw new SQLException("executeUpdate() cannot be called with a SELECT/SHOW/DESCRIBE/EXPLAIN statement", ExceptionUtils.SQL_STATE_SQL_ERROR);
225224
}
@@ -344,18 +343,16 @@ public void setCursorName(String name) throws SQLException {
344343
@Override
345344
public boolean execute(String sql) throws SQLException {
346345
checkClosed();
347-
return execute(sql, new QuerySettings().setDatabase(schema));
346+
return executeImpl(sql, parseStatementType(sql), new QuerySettings().setDatabase(schema));
348347
}
349348

350-
public boolean execute(String sql, QuerySettings settings) throws SQLException {
349+
public boolean executeImpl(String sql, StatementType type, QuerySettings settings) throws SQLException {
351350
checkClosed();
352-
StatementType type = parseStatementType(sql);
353-
354351
if (type == StatementType.SELECT || type == StatementType.SHOW || type == StatementType.DESCRIBE || type == StatementType.EXPLAIN) {
355-
executeQuery(sql, settings); // keep open to allow getResultSet()
352+
executeQueryImpl(sql, settings); // keep open to allow getResultSet()
356353
return true;
357354
} else if(type == StatementType.SET) {
358-
executeUpdate(sql, settings);
355+
executeUpdateImpl(sql, type, settings);
359356
//SET ROLE
360357
List<String> tokens = JdbcUtils.tokenizeSQL(sql);
361358
if (JdbcUtils.containsIgnoresCase(tokens, "ROLE")) {
@@ -379,14 +376,14 @@ public boolean execute(String sql, QuerySettings settings) throws SQLException {
379376
}
380377
return false;
381378
} else if (type == StatementType.USE) {
382-
executeUpdate(sql, settings);
379+
executeUpdateImpl(sql, type, settings);
383380
//USE Database
384381
List<String> tokens = JdbcUtils.tokenizeSQL(sql);
385382
this.schema = tokens.get(1).replace("\"", "");
386383
LOG.debug("Changed statement schema {}", schema);
387384
return false;
388385
} else {
389-
executeUpdate(sql, settings);
386+
executeUpdateImpl(sql, type, settings);
390387
return false;
391388
}
392389
}

jdbc-v2/src/main/java/com/clickhouse/jdbc/internal/ExceptionUtils.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,10 @@ public final class ExceptionUtils {
2424
public static final String SQL_STATE_INVALID_SCHEMA = "3F000";
2525
public static final String SQL_STATE_INVALID_TX_STATE = "25000";
2626
public static final String SQL_STATE_DATA_EXCEPTION = "22000";
27+
// Used only when feature is no supported
2728
public static final String SQL_STATE_FEATURE_NOT_SUPPORTED = "0A000";
29+
// Used only when method is called on wrong object type (for example, PreparedStatement.addBatch(String))
30+
public static final String SQL_STATE_WRONG_OBJECT_TYPE = "42809";
2831

2932
private ExceptionUtils() {}//Private constructor
3033

jdbc-v2/src/test/java/com/clickhouse/jdbc/PreparedStatementTest.java

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.clickhouse.jdbc;
22

3+
import com.clickhouse.client.api.query.QuerySettings;
34
import org.apache.commons.lang3.RandomStringUtils;
45
import org.testng.Assert;
56
import org.testng.annotations.DataProvider;
@@ -11,6 +12,7 @@
1112
import java.sql.PreparedStatement;
1213
import java.sql.ResultSet;
1314
import java.sql.ResultSetMetaData;
15+
import java.sql.SQLException;
1416
import java.sql.Statement;
1517
import java.sql.Types;
1618
import java.util.Arrays;
@@ -580,4 +582,39 @@ void testClearParameters() throws Exception {
580582
ps.execute();
581583
}
582584
}
585+
586+
@Test
587+
void testMethodsNotAllowedToBeCalled() throws Exception {
588+
/* Story About Broken API
589+
* There is a Statement interface. It is designed to operate with single statements.
590+
* So there are method like execute(String) and addBatch(String).
591+
* Some statements may be repeated over and over again. And they should be constructed
592+
* over and over again. PreparedStatement was created to solve the issue by accepting
593+
* an SQL statement as constructor parameter and making its method work in context of
594+
* one, prepared SQL statement.
595+
* But someone missed their OOP classes and done this:
596+
* "interface PreparedStatement extends Statement"
597+
* and
598+
* declared some method from Statement interface not to be called on PreparedStatement
599+
* instances.
600+
* That is how today we have a great confusion and have to check it in all implementations.
601+
*/
602+
String sql = "SELECT number FROM system.numbers WHERE number = ?";
603+
try (Connection conn = getJdbcConnection();
604+
PreparedStatementImpl ps = (PreparedStatementImpl) conn.prepareStatement(sql)) {
605+
606+
Assert.assertThrows(SQLException.class, () -> ps.addBatch(sql));
607+
Assert.assertThrows(SQLException.class, () -> ps.executeQuery(sql));
608+
Assert.assertThrows(SQLException.class, () -> ps.executeQueryImpl(sql, null));
609+
Assert.assertThrows(SQLException.class, () -> ps.execute(sql));
610+
Assert.assertThrows(SQLException.class, () -> ps.execute(sql, new int[]{0}));
611+
Assert.assertThrows(SQLException.class, () -> ps.execute(sql, new String[]{""}));
612+
Assert.assertThrows(SQLException.class, () -> ps.executeUpdate(sql));
613+
Assert.assertThrows(SQLException.class, () -> ps.executeUpdate(sql, new int[]{0}));
614+
Assert.assertThrows(SQLException.class, () -> ps.executeUpdate(sql, new String[]{""}));
615+
Assert.assertThrows(SQLException.class, () -> ps.executeLargeUpdate(sql));
616+
Assert.assertThrows(SQLException.class, () -> ps.executeLargeUpdate(sql, new int[]{0}));
617+
Assert.assertThrows(SQLException.class, () -> ps.executeLargeUpdate(sql, new String[]{""}));
618+
}
619+
}
583620
}

0 commit comments

Comments
 (0)