Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding the RowBinary support #1982

Closed
wants to merge 1 commit into from
Closed
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
214 changes: 166 additions & 48 deletions jdbc-v2/src/main/java/com/clickhouse/jdbc/PreparedStatementImpl.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package com.clickhouse.jdbc;

import com.clickhouse.client.api.insert.InsertSettings;
import com.clickhouse.client.api.metadata.TableSchema;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand Down Expand Up @@ -33,9 +35,11 @@
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeFormatterBuilder;
import java.time.temporal.ChronoField;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.GregorianCalendar;
import java.util.List;
import java.util.Map;

public class PreparedStatementImpl extends StatementImpl implements PreparedStatement, JdbcV2Wrapper {
Expand All @@ -48,20 +52,50 @@ public class PreparedStatementImpl extends StatementImpl implements PreparedStat
.appendPattern("yyyy-MM-dd HH:mm:ss").appendFraction(ChronoField.NANO_OF_SECOND, 0, 9, true).toFormatter();

String originalSql;
String [] sqlSegments;
Object [] parameters;
public PreparedStatementImpl(ConnectionImpl connection, String sql) {
String[] sqlSegments;
Object[] parameters;
int setCalls;//The number of times set* methods have been called

//For insertRowBinary fanciness
StatementType statementType;
boolean insertRowBinary;
List<Object> insertRowBinaryData;
String tableName;
TableSchema tableSchema;


public PreparedStatementImpl(ConnectionImpl connection, String sql) throws SQLException {
super(connection);
this.originalSql = sql;
//Split the sql string into an array of strings around question mark tokens
this.sqlSegments = sql.split("\\?");

//Create an array of objects to store the parameters
if (originalSql.contains("?")) {
int count = originalSql.length() - originalSql.replace("?", "").length();
this.parameters = new Object[count];
} else {
this.parameters = new Object[0];

try {
this.originalSql = sql;
this.setCalls = 0;
this.statementType = parseStatementType(sql);
this.insertRowBinary = false;
this.insertRowBinaryData = new ArrayList<>();
this.tableName = null;
this.tableSchema = null;

//Split the sql string into an array of strings around question mark tokens
this.sqlSegments = sql.split("\\?");

//Create an array of objects to store the parameters
if (originalSql.contains("?")) {
int count = originalSql.length() - originalSql.replace("?", "").length();
this.parameters = new Object[count];
} else {
if (statementType != StatementType.INSERT) {
throw new SQLException("SQL prepared statement does not contain any placeholders.");
}

this.parameters = new Object[0];
this.insertRowBinary = true;//We only use this when no parameters are present and the statement is an insert
this.tableName = parseTableName(sql);
this.tableSchema = connection.client.getTableSchema(tableName);
}
} catch (Exception e) {
LOG.error("Error creating prepared statement", e);
throw new SQLException("Error creating prepared statement", e);
}
}

Expand All @@ -77,16 +111,91 @@ private String compileSql() {
return sb.toString();
}

private boolean enoughParameters() {
return this.parameters.length == setCalls || insertRowBinary;
}

@Override
public ResultSet executeQuery() throws SQLException {
checkClosed();
return executeQuery(compileSql());

if (!enoughParameters()) {
throw new SQLException("The number of parameters does not match the number of placeholders in the SQL string.");
}

return super.executeQuery(compileSql());
}

@Override
public ResultSet executeQuery(String sql) throws SQLException {
checkClosed();
throw new SQLException("executeQuery(String) is not supported in PreparedStatements.");
}

@Override
public int executeUpdate() throws SQLException {
checkClosed();
return executeUpdate(compileSql());
if (insertRowBinary) {
return (int) super.executeInsert(tableName, insertRowBinaryData, new InsertSettings());
}

if (!enoughParameters()) {
throw new SQLException("The number of parameters does not match the number of placeholders in the SQL string.");
}

return super.executeUpdate(compileSql());
}

@Override
public int executeUpdate(String sql) throws SQLException {
checkClosed();
throw new SQLException("executeUpdate(String) is not supported in PreparedStatements.");
}

@Override
public boolean execute() throws SQLException {
checkClosed();
if (insertRowBinary) {
super.executeInsert(tableName, insertRowBinaryData, new InsertSettings());
return false;
}

if (!enoughParameters()) {
throw new SQLException("The number of parameters does not match the number of placeholders in the SQL string.");
}
return super.execute(compileSql());
}

@Override
public boolean execute(String sql) throws SQLException {
checkClosed();
throw new SQLException("execute(String) is not supported in PreparedStatements.");
}

@Override
public void addBatch() throws SQLException {
checkClosed();
if (!insertRowBinary) {//We ignore this for insertRowBinary
if (!enoughParameters()) {
throw new SQLException("The number of parameters does not match the number of placeholders in the SQL string.");
}
super.addBatch(compileSql());
}
}

@Override
public void addBatch(String sql) throws SQLException {
checkClosed();
throw new SQLException("addBatch(String) is not supported in PreparedStatements.");
}

@Override
public int[] executeBatch() throws SQLException {
checkClosed();
if (insertRowBinary) {
return new int[] { (int) super.executeInsert(tableName, insertRowBinaryData, new InsertSettings()) };
}
return super.executeBatch();
}

@Override
Expand Down Expand Up @@ -152,6 +261,7 @@ public void setString(int parameterIndex, String x) throws SQLException {
@Override
public void setBytes(int parameterIndex, byte[] x) throws SQLException {
checkClosed();

parameters[parameterIndex - 1] = encodeObject(x);
}

Expand Down Expand Up @@ -194,11 +304,9 @@ public void setBinaryStream(int parameterIndex, InputStream x, int length) throw
@Override
public void clearParameters() throws SQLException {
checkClosed();
if (originalSql.contains("?")) {
this.parameters = new Object[sqlSegments.length];
} else {
this.parameters = new Object[0];
}
int paramCount = parameters.length;
this.parameters = new Object[paramCount];
this.setCalls = 0;
}

@Override
Expand All @@ -213,18 +321,6 @@ public void setObject(int parameterIndex, Object x) throws SQLException {
setObject(parameterIndex, x, Types.OTHER);
}

@Override
public boolean execute() throws SQLException {
checkClosed();
return execute(compileSql());
}

@Override
public void addBatch() throws SQLException {
checkClosed();
addBatch(compileSql());
}

@Override
public void setCharacterStream(int parameterIndex, Reader x, int length) throws SQLException {
checkClosed();
Expand All @@ -240,13 +336,13 @@ public void setRef(int parameterIndex, Ref x) throws SQLException {
@Override
public void setBlob(int parameterIndex, Blob x) throws SQLException {
checkClosed();
parameters[parameterIndex - 1] = encodeObject(x);
throw new SQLFeatureNotSupportedException("Blob is not supported.");
}

@Override
public void setClob(int parameterIndex, Clob x) throws SQLException {
checkClosed();
parameters[parameterIndex - 1] = encodeObject(x);
throw new SQLFeatureNotSupportedException("Clob is not supported.");
}

@Override
Expand Down Expand Up @@ -342,31 +438,31 @@ public void setNCharacterStream(int parameterIndex, Reader x, long length) throw
@Override
public void setNClob(int parameterIndex, NClob x) throws SQLException {
checkClosed();
parameters[parameterIndex - 1] = encodeObject(x);
throw new SQLFeatureNotSupportedException("NClob is not supported.");
}

@Override
public void setClob(int parameterIndex, Reader x, long length) throws SQLException {
checkClosed();
parameters[parameterIndex - 1] = encodeObject(x);
throw new SQLFeatureNotSupportedException("Clob is not supported.");
}

@Override
public void setBlob(int parameterIndex, InputStream x, long length) throws SQLException {
checkClosed();
parameters[parameterIndex - 1] = encodeObject(x);
throw new SQLFeatureNotSupportedException("Blob is not supported.");
}

@Override
public void setNClob(int parameterIndex, Reader x, long length) throws SQLException {
checkClosed();
parameters[parameterIndex - 1] = encodeObject(x);
throw new SQLFeatureNotSupportedException("NClob is not supported.");
}

@Override
public void setSQLXML(int parameterIndex, SQLXML x) throws SQLException {
checkClosed();
parameters[parameterIndex - 1] = encodeObject(x);
throw new SQLFeatureNotSupportedException("SQLXML is not supported.");
}

@Override
Expand Down Expand Up @@ -420,25 +516,40 @@ public void setNCharacterStream(int parameterIndex, Reader x) throws SQLExceptio
@Override
public void setClob(int parameterIndex, Reader x) throws SQLException {
checkClosed();
parameters[parameterIndex - 1] = encodeObject(x);
throw new SQLFeatureNotSupportedException("Clob is not supported.");
}

@Override
public void setBlob(int parameterIndex, InputStream x) throws SQLException {
checkClosed();
parameters[parameterIndex - 1] = encodeObject(x);
throw new SQLFeatureNotSupportedException("Blob is not supported.");
}

@Override
public void setNClob(int parameterIndex, Reader x) throws SQLException {
checkClosed();
parameters[parameterIndex - 1] = encodeObject(x);
throw new SQLFeatureNotSupportedException("NClob is not supported.");
}

@Override
public void setObject(int parameterIndex, Object x, SQLType targetSqlType, int scaleOrLength) throws SQLException {
checkClosed();
parameters[parameterIndex - 1] = encodeObject(x);

try {
if (!insertRowBinary) {
parameters[parameterIndex - 1] = encodeObject(x);
} else {
if (insertRowBinaryData.isEmpty()) {//Register the first object
Class<?> clazz = x.getClass();
connection.client.register(clazz, tableSchema);
}

insertRowBinaryData.add(x);
}
} catch (Exception e) {
LOG.error("Error setting object", e);
throw new SQLException("Error setting object", e);
}
}

@Override
Expand All @@ -453,7 +564,14 @@ public long executeLargeUpdate() throws SQLException {
return PreparedStatement.super.executeLargeUpdate();
}

private static String encodeObject(Object x) throws SQLException {
private String encodeObject(Object x) throws SQLException {
return encodeObject(x, true);
}
private String encodeObject(Object x, boolean shouldIncrementCount) throws SQLException {
if (shouldIncrementCount) {
setCalls++;
}

try {
if (x == null) {
return "NULL";
Expand All @@ -476,8 +594,8 @@ private static String encodeObject(Object x) throws SQLException {
} else if (x instanceof Array) {
StringBuilder listString = new StringBuilder();
listString.append("[");
for (Object item : (Object[])((Array) x).getArray()) {
listString.append(encodeObject(item)).append(", ");
for (Object item : (Object[]) ((Array) x).getArray()) {
listString.append(encodeObject(item, false)).append(", ");
}
listString.delete(listString.length() - 2, listString.length());
listString.append("]");
Expand All @@ -487,7 +605,7 @@ private static String encodeObject(Object x) throws SQLException {
StringBuilder listString = new StringBuilder();
listString.append("[");
for (Object item : (Collection<?>) x) {
listString.append(encodeObject(item)).append(", ");
listString.append(encodeObject(item, false)).append(", ");
}
listString.delete(listString.length() - 2, listString.length());
listString.append("]");
Expand All @@ -498,7 +616,7 @@ private static String encodeObject(Object x) throws SQLException {
StringBuilder mapString = new StringBuilder();
mapString.append("{");
for (Object key : tmpMap.keySet()) {
mapString.append(encodeObject(key)).append(": ").append(encodeObject(tmpMap.get(key))).append(", ");
mapString.append(encodeObject(key, false)).append(": ").append(encodeObject(tmpMap.get(key), false)).append(", ");
}
mapString.delete(mapString.length() - 2, mapString.length());
mapString.append("}");
Expand Down
Loading
Loading