Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
147 changes: 134 additions & 13 deletions jdbc-v2/src/main/java/com/clickhouse/jdbc/PreparedStatementImpl.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.clickhouse.jdbc;

import com.clickhouse.client.api.metadata.TableSchema;
import com.clickhouse.client.api.query.QuerySettings;
import com.clickhouse.data.Tuple;
import com.clickhouse.jdbc.internal.ExceptionUtils;
import com.clickhouse.jdbc.internal.JdbcUtils;
Expand Down Expand Up @@ -101,13 +102,14 @@ private String compileSql(String []segments) {
@Override
public ResultSet executeQuery() throws SQLException {
checkClosed();
return executeQuery(compileSql(sqlSegments));
return super.executeQueryImpl(compileSql(sqlSegments), new QuerySettings().setDatabase(connection.getSchema()));
}

@Override
public int executeUpdate() throws SQLException {
checkClosed();
return executeUpdate(compileSql(sqlSegments));
return super.executeUpdateImpl(compileSql(sqlSegments), statementType,
new QuerySettings().setDatabase(connection.getSchema()));
}

@Override
Expand Down Expand Up @@ -234,17 +236,18 @@ public void setObject(int parameterIndex, Object x) throws SQLException {
@Override
public boolean execute() throws SQLException {
checkClosed();
return execute(compileSql(sqlSegments));
return super.executeImpl(compileSql(sqlSegments), statementType,
new QuerySettings().setDatabase(connection.getSchema()));
}

@Override
public void addBatch() throws SQLException {
checkClosed();
if (statementType == StatementType.INSERT) {
// adding values to the end of big INSERT statement.
addBatch(compileSql(valueSegments));
super.addBatch(compileSql(valueSegments));
} else {
addBatch(compileSql(sqlSegments));
super.addBatch(compileSql(sqlSegments));
}
}

Expand All @@ -259,7 +262,8 @@ public int[] executeBatch() throws SQLException {
sb.append(sql).append(",");
}
sb.setCharAt(sb.length() - 1, ';');
int rowsInserted = executeUpdate(sb.toString());
int rowsInserted = executeUpdateImpl(sb.toString(), statementType,
new QuerySettings().setDatabase(connection.getSchema()));
// clear batch and re-add insert into
int[] results = new int[batch.size()];
if (rowsInserted == batch.size()) {
Expand All @@ -274,18 +278,22 @@ public int[] executeBatch() throws SQLException {
return results;
} else {
// run executeBatch
return super.executeBatch();
return executeBatchImpl().stream().mapToInt(Integer::intValue).toArray();
}
}

@Override
public long[] executeLargeBatch() throws SQLException {
int[] results = executeBatch();
long[] longResults = new long[results.length];
for (int i = 0; i < results.length; i++) {
longResults[i] = results[i];
return executeBatchImpl().stream().mapToLong(Integer::longValue).toArray();
}

private List<Integer> executeBatchImpl() throws SQLException {
List<Integer> results = new ArrayList<>();
QuerySettings settings = new QuerySettings().setDatabase(connection.getSchema());
for (String sql : batch) {
results.add(executeUpdateImpl(sql, statementType, settings));
}
return longResults;
return results;
}

@Override
Expand Down Expand Up @@ -412,7 +420,8 @@ public ParameterMetaData getParameterMetaData() throws SQLException {
@Override
public void setRowId(int parameterIndex, RowId x) throws SQLException {
checkClosed();
parameters[parameterIndex - 1] = encodeObject(x);
throw new SQLException("ROWID type is not supported by ClickHouse.",
ExceptionUtils.SQL_STATE_FEATURE_NOT_SUPPORTED);
}

@Override
Expand Down Expand Up @@ -540,6 +549,118 @@ public long executeLargeUpdate() throws SQLException {
return executeUpdate();
}

@Override
public final void addBatch(String sql) throws SQLException {
checkClosed();
throw new SQLException(
"addBatch(String) cannot be called in PreparedStatement or CallableStatement!",
ExceptionUtils.SQL_STATE_WRONG_OBJECT_TYPE);
}

@Override
public final boolean execute(String sql) throws SQLException {
checkClosed();
throw new SQLException(
"execute(String) cannot be called in PreparedStatement or CallableStatement!",
ExceptionUtils.SQL_STATE_WRONG_OBJECT_TYPE);
}

@Override
public final boolean execute(String sql, int autoGeneratedKeys) throws SQLException {
checkClosed();
throw new SQLException(
"execute(String, int) cannot be called in PreparedStatement or CallableStatement!",
ExceptionUtils.SQL_STATE_WRONG_OBJECT_TYPE);
}

@Override
public final boolean execute(String sql, int[] columnIndexes) throws SQLException {
checkClosed();
throw new SQLException(
"execute(String, int[]) cannot be called in PreparedStatement or CallableStatement!",
ExceptionUtils.SQL_STATE_WRONG_OBJECT_TYPE);
}

@Override
public final boolean execute(String sql, String[] columnNames) throws SQLException {
checkClosed();
throw new SQLException(
"execute(String, String[]) cannot be called in PreparedStatement or CallableStatement!",
ExceptionUtils.SQL_STATE_WRONG_OBJECT_TYPE);
}

@Override
public final long executeLargeUpdate(String sql) throws SQLException {
checkClosed();
throw new SQLException(
"executeLargeUpdate(String) cannot be called in PreparedStatement or CallableStatement!",
ExceptionUtils.SQL_STATE_WRONG_OBJECT_TYPE);
}

@Override
public final long executeLargeUpdate(String sql, int autoGeneratedKeys) throws SQLException {
checkClosed();
throw new SQLException(
"executeLargeUpdate(String, int) cannot be called in PreparedStatement or CallableStatement!",
ExceptionUtils.SQL_STATE_WRONG_OBJECT_TYPE);
}

@Override
public final long executeLargeUpdate(String sql, int[] columnIndexes) throws SQLException {
checkClosed();
throw new SQLException(
"executeLargeUpdate(String, int[]) cannot be called in PreparedStatement or CallableStatement!",
ExceptionUtils.SQL_STATE_WRONG_OBJECT_TYPE);
}

@Override
public final long executeLargeUpdate(String sql, String[] columnNames) throws SQLException {
checkClosed();
throw new SQLException(
"executeLargeUpdate(String, String[]) cannot be called in PreparedStatement or CallableStatement!",
ExceptionUtils.SQL_STATE_WRONG_OBJECT_TYPE);
}

@Override
public final ResultSet executeQuery(String sql) throws SQLException {
checkClosed();
throw new SQLException(
"executeQuery(String) cannot be called in PreparedStatement or CallableStatement!",
ExceptionUtils.SQL_STATE_WRONG_OBJECT_TYPE);
}

@Override
public final int executeUpdate(String sql) throws SQLException {
checkClosed();
throw new SQLException(
"executeUpdate(String) cannot be called in PreparedStatement or CallableStatement!",
ExceptionUtils.SQL_STATE_WRONG_OBJECT_TYPE);
}

@Override
public final int executeUpdate(String sql, int autoGeneratedKeys) throws SQLException {
checkClosed();
throw new SQLException(
"executeUpdate(String, int) cannot be called in PreparedStatement or CallableStatement!",
ExceptionUtils.SQL_STATE_WRONG_OBJECT_TYPE);
}

@Override
public final int executeUpdate(String sql, int[] columnIndexes) throws SQLException {
checkClosed();
throw new SQLException(
"executeUpdate(String, int[]) cannot be called in PreparedStatement or CallableStatement!",
ExceptionUtils.SQL_STATE_WRONG_OBJECT_TYPE);
}

@Override
public final int executeUpdate(String sql, String[] columnNames) throws SQLException {
checkClosed();
throw new SQLException(
"executeUpdate(String, String[]) cannot be called in PreparedStatement or CallableStatement!",
ExceptionUtils.SQL_STATE_WRONG_OBJECT_TYPE);
}

private static String encodeObject(Object x) throws SQLException {
LOG.trace("Encoding object: {}", x);

Expand Down
27 changes: 12 additions & 15 deletions jdbc-v2/src/main/java/com/clickhouse/jdbc/StatementImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ protected void checkClosed() throws SQLException {
}
}

protected enum StatementType {
public enum StatementType {
SELECT, INSERT, DELETE, UPDATE, CREATE, DROP, ALTER, TRUNCATE, USE, SHOW, DESCRIBE, EXPLAIN, SET, KILL, OTHER, INSERT_INTO_SELECT
}

Expand Down Expand Up @@ -148,7 +148,7 @@ protected String getLastSql() {
@Override
public ResultSet executeQuery(String sql) throws SQLException {
checkClosed();
return executeQuery(sql, new QuerySettings().setDatabase(schema));
return executeQueryImpl(sql, new QuerySettings().setDatabase(schema));
}

private void closePreviousResultSet() {
Expand All @@ -165,7 +165,7 @@ private void closePreviousResultSet() {
}
}

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

public int executeUpdate(String sql, QuerySettings settings) throws SQLException {
// TODO: close current result set?
protected int executeUpdateImpl(String sql, StatementType type, QuerySettings settings) throws SQLException {
checkClosed();
StatementType type = parseStatementType(sql);

if (type == StatementType.SELECT || type == StatementType.SHOW || type == StatementType.DESCRIBE || type == StatementType.EXPLAIN) {
throw new SQLException("executeUpdate() cannot be called with a SELECT/SHOW/DESCRIBE/EXPLAIN statement", ExceptionUtils.SQL_STATE_SQL_ERROR);
}
Expand Down Expand Up @@ -344,18 +343,16 @@ public void setCursorName(String name) throws SQLException {
@Override
public boolean execute(String sql) throws SQLException {
checkClosed();
return execute(sql, new QuerySettings().setDatabase(schema));
return executeImpl(sql, parseStatementType(sql), new QuerySettings().setDatabase(schema));
}

public boolean execute(String sql, QuerySettings settings) throws SQLException {
public boolean executeImpl(String sql, StatementType type, QuerySettings settings) throws SQLException {
checkClosed();
StatementType type = parseStatementType(sql);

if (type == StatementType.SELECT || type == StatementType.SHOW || type == StatementType.DESCRIBE || type == StatementType.EXPLAIN) {
executeQuery(sql, settings); // keep open to allow getResultSet()
executeQueryImpl(sql, settings); // keep open to allow getResultSet()
return true;
} else if(type == StatementType.SET) {
executeUpdate(sql, settings);
executeUpdateImpl(sql, type, settings);
//SET ROLE
List<String> tokens = JdbcUtils.tokenizeSQL(sql);
if (JdbcUtils.containsIgnoresCase(tokens, "ROLE")) {
Expand All @@ -379,14 +376,14 @@ public boolean execute(String sql, QuerySettings settings) throws SQLException {
}
return false;
} else if (type == StatementType.USE) {
executeUpdate(sql, settings);
executeUpdateImpl(sql, type, settings);
//USE Database
List<String> tokens = JdbcUtils.tokenizeSQL(sql);
this.schema = tokens.get(1).replace("\"", "");
LOG.debug("Changed statement schema {}", schema);
return false;
} else {
executeUpdate(sql, settings);
executeUpdateImpl(sql, type, settings);
return false;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,10 @@ public final class ExceptionUtils {
public static final String SQL_STATE_INVALID_SCHEMA = "3F000";
public static final String SQL_STATE_INVALID_TX_STATE = "25000";
public static final String SQL_STATE_DATA_EXCEPTION = "22000";
// Used only when feature is not supported
public static final String SQL_STATE_FEATURE_NOT_SUPPORTED = "0A000";
// Used only when method is called on wrong object type (for example, PreparedStatement.addBatch(String))
public static final String SQL_STATE_WRONG_OBJECT_TYPE = "42809";

private ExceptionUtils() {}//Private constructor

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.clickhouse.jdbc;

import com.clickhouse.client.api.query.QuerySettings;
import org.apache.commons.lang3.RandomStringUtils;
import org.testng.Assert;
import org.testng.annotations.DataProvider;
Expand All @@ -11,6 +12,7 @@
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Types;
import java.util.*;
Expand Down Expand Up @@ -628,4 +630,39 @@ void testWriteCollection() throws Exception {
}

}

@Test
void testMethodsNotAllowedToBeCalled() throws Exception {
/* Story About Broken API
* There is a Statement interface. It is designed to operate with single statements.
* So there are method like execute(String) and addBatch(String).
* Some statements may be repeated over and over again. And they should be constructed
* over and over again. PreparedStatement was created to solve the issue by accepting
* an SQL statement as constructor parameter and making its method work in context of
* one, prepared SQL statement.
* But someone missed their OOP classes and done this:
* "interface PreparedStatement extends Statement"
* and
* declared some method from Statement interface not to be called on PreparedStatement
* instances.
* That is how today we have a great confusion and have to check it in all implementations.
*/
String sql = "SELECT number FROM system.numbers WHERE number = ?";
try (Connection conn = getJdbcConnection();
PreparedStatementImpl ps = (PreparedStatementImpl) conn.prepareStatement(sql)) {

Assert.assertThrows(SQLException.class, () -> ps.addBatch(sql));
Assert.assertThrows(SQLException.class, () -> ps.executeQuery(sql));
Assert.assertThrows(SQLException.class, () -> ps.executeQueryImpl(sql, null));
Assert.assertThrows(SQLException.class, () -> ps.execute(sql));
Assert.assertThrows(SQLException.class, () -> ps.execute(sql, new int[]{0}));
Assert.assertThrows(SQLException.class, () -> ps.execute(sql, new String[]{""}));
Assert.assertThrows(SQLException.class, () -> ps.executeUpdate(sql));
Assert.assertThrows(SQLException.class, () -> ps.executeUpdate(sql, new int[]{0}));
Assert.assertThrows(SQLException.class, () -> ps.executeUpdate(sql, new String[]{""}));
Assert.assertThrows(SQLException.class, () -> ps.executeLargeUpdate(sql));
Assert.assertThrows(SQLException.class, () -> ps.executeLargeUpdate(sql, new int[]{0}));
Assert.assertThrows(SQLException.class, () -> ps.executeLargeUpdate(sql, new String[]{""}));
}
}
}
Loading