Skip to content

Commit 118252c

Browse files
committed
improvements:
- PreparedStatementImpl: getMetaData returns result set metadata using describe query - StatementImpl: maxRows handling - Driver: unload method fixed (DriverManager search driver for unregister by reference equality) - HttpAPIClientHelper: added sslmode option (presents in v1), support to skip ssl validation fixes: - PreparedStatementImpl: ignores question mark (?) in comments, string literals and quoted identifiers - StatementImpl: multiline block comments trim - HttpAPIClientHelper: null pointer exception with unpooled connection (poolControl is null)
1 parent da90573 commit 118252c

File tree

9 files changed

+220
-17
lines changed

9 files changed

+220
-17
lines changed

client-v2/src/main/java/com/clickhouse/client/api/ClientConfigProperties.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,8 @@ public enum ClientConfigProperties {
9292

9393
SSL_KEY_STORE_PASSWORD("key_store_password"),
9494

95+
SSL_MODE("sslmode", "strict", Arrays.asList("strict", "none")),
96+
9597
SSL_KEY("ssl_key"),
9698

9799
CA_CERTIFICATE("sslrootcert"),

client-v2/src/main/java/com/clickhouse/client/api/internal/HttpAPIClientHelper.java

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,11 @@
5858
import org.slf4j.Logger;
5959
import org.slf4j.LoggerFactory;
6060

61+
import javax.net.ssl.KeyManager;
6162
import javax.net.ssl.SSLContext;
6263
import javax.net.ssl.SSLException;
64+
import javax.net.ssl.TrustManager;
65+
import javax.net.ssl.X509TrustManager;
6366
import java.io.IOException;
6467
import java.io.InputStream;
6568
import java.io.OutputStream;
@@ -74,7 +77,10 @@
7477
import java.net.URL;
7578
import java.net.UnknownHostException;
7679
import java.nio.charset.StandardCharsets;
80+
import java.security.KeyManagementException;
7781
import java.security.NoSuchAlgorithmException;
82+
import java.security.SecureRandom;
83+
import java.security.cert.X509Certificate;
7884
import java.util.Base64;
7985
import java.util.Collection;
8086
import java.util.Collections;
@@ -169,6 +175,13 @@ public SSLContext createSSLContext() {
169175
} catch (SSLException e) {
170176
throw new ClientMisconfigurationException("Failed to create SSL context from certificates", e);
171177
}
178+
} else if ("none".equals(chConfiguration.get(ClientConfigProperties.SSL_MODE.getKey()))) {
179+
try {
180+
sslContext = SSLContext.getInstance("TLS");
181+
sslContext.init(new KeyManager[0], new TrustManager[]{new TrustAllManager()}, new SecureRandom());
182+
} catch (NoSuchAlgorithmException | KeyManagementException e) {
183+
throw new ClientException("Failed to create none validating SSL context", e);
184+
}
172185
}
173186
return sslContext;
174187
}
@@ -380,7 +393,7 @@ public Exception readError(ClassicHttpResponse httpResponse) {
380393

381394
public ClassicHttpResponse executeRequest(ClickHouseNode server, Map<String, Object> requestConfig, LZ4Factory lz4Factory,
382395
IOCallback<OutputStream> writeCallback) throws IOException {
383-
if (timeToPoolVent.get() < System.currentTimeMillis()) {
396+
if (poolControl != null && timeToPoolVent.get() < System.currentTimeMillis()) {
384397
timeToPoolVent.set(System.currentTimeMillis() + POOL_VENT_TIMEOUT);
385398
poolControl.closeExpired();
386399
}
@@ -828,4 +841,22 @@ public long getTime() {
828841
return count > 0 ? runningAverage / count : 0;
829842
}
830843
}
844+
845+
private static final class TrustAllManager implements X509TrustManager {
846+
847+
@Override
848+
public void checkClientTrusted(X509Certificate[] chain, String authType) {
849+
// ignore
850+
}
851+
852+
@Override
853+
public void checkServerTrusted(X509Certificate[] chain, String authType) {
854+
// ignore
855+
}
856+
857+
@Override
858+
public X509Certificate[] getAcceptedIssuers() {
859+
return new X509Certificate[0];
860+
}
861+
}
831862
}

client-v2/src/test/java/com/clickhouse/client/ClientTests.java

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

33
import com.clickhouse.client.api.Client;
4+
import com.clickhouse.client.api.ClientConfigProperties;
45
import com.clickhouse.client.api.ClientException;
56
import com.clickhouse.client.api.enums.Protocol;
67
import com.clickhouse.client.api.query.GenericRecord;
@@ -64,6 +65,12 @@ private static Client[] secureClientProvider() throws Exception {
6465
.setPassword("")
6566
.setRootCertificate("containers/clickhouse-server/certs/localhost.crt")
6667
.build(),
68+
new Client.Builder()
69+
.addEndpoint("https://" + node.getHost() + ":" + node.getPort())
70+
.setUsername("default")
71+
.setPassword("")
72+
.setOption(ClientConfigProperties.SSL_MODE.getKey(), "none")
73+
.build(),
6774
new Client.Builder()
6875
.addEndpoint(Protocol.HTTP, node.getHost(), node.getPort(), true)
6976
.setUsername("default")
@@ -108,6 +115,13 @@ public void testPing() {
108115
}
109116
}
110117

118+
@Test
119+
public void testPingUnpooled() {
120+
try (Client client = newClient().enableConnectionPool(false).build()) {
121+
Assert.assertTrue(client.ping());
122+
}
123+
}
124+
111125
@Test
112126
public void testPingFailure() {
113127
try (Client client = new Client.Builder()

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

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -98,15 +98,15 @@ public Driver(DataSourceImpl dataSourceImpl) {
9898

9999
public static void load() {
100100
try {
101-
DriverManager.registerDriver(new Driver());
101+
DriverManager.registerDriver(DriverHolder.INSTANCE);
102102
} catch (SQLException e) {
103103
log.error("Failed to register ClickHouse JDBC driver", e);
104104
}
105105
}
106106

107107
public static void unload() {
108108
try {
109-
DriverManager.deregisterDriver(new Driver());
109+
DriverManager.deregisterDriver(DriverHolder.INSTANCE);
110110
} catch (SQLException e) {
111111
log.error("Failed to deregister ClickHouse JDBC driver", e);
112112
}
@@ -163,4 +163,8 @@ public static String chSettingKey(String key) {
163163
public java.util.logging.Logger getParentLogger() throws SQLFeatureNotSupportedException {
164164
throw new SQLFeatureNotSupportedException("Method not supported", ExceptionUtils.SQL_STATE_FEATURE_NOT_SUPPORTED);
165165
}
166+
167+
private static final class DriverHolder {
168+
private static final Driver INSTANCE = new Driver();
169+
}
166170
}

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

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

3+
import com.clickhouse.client.api.metadata.TableSchema;
4+
import com.clickhouse.data.ClickHouseColumn;
35
import com.clickhouse.data.Tuple;
46
import com.clickhouse.jdbc.internal.ExceptionUtils;
57
import org.slf4j.Logger;
@@ -26,6 +28,7 @@
2628
import java.sql.SQLFeatureNotSupportedException;
2729
import java.sql.SQLType;
2830
import java.sql.SQLXML;
31+
import java.sql.Statement;
2932
import java.sql.Time;
3033
import java.sql.Timestamp;
3134
import java.sql.Types;
@@ -68,7 +71,7 @@ public PreparedStatementImpl(ConnectionImpl connection, String sql) throws SQLEx
6871
super(connection);
6972
this.originalSql = sql.trim();
7073
//Split the sql string into an array of strings around question mark tokens
71-
this.sqlSegments = originalSql.split("\\?");
74+
this.sqlSegments = splitStatement(originalSql);
7275
this.statementType = parseStatementType(originalSql);
7376

7477
if (statementType == StatementType.INSERT) {
@@ -77,13 +80,7 @@ public PreparedStatementImpl(ConnectionImpl connection, String sql) throws SQLEx
7780
}
7881

7982
//Create an array of objects to store the parameters
80-
if (originalSql.contains("?")) {
81-
int count = originalSql.length() - originalSql.replace("?", "").length();
82-
this.parameters = new Object[count];
83-
} else {
84-
this.parameters = new Object[0];
85-
}
86-
83+
this.parameters = new Object[sqlSegments.length - 1];
8784
this.defaultCalendar = connection.defaultCalendar;
8885
}
8986

@@ -305,7 +302,26 @@ public void setArray(int parameterIndex, Array x) throws SQLException {
305302
@Override
306303
public ResultSetMetaData getMetaData() throws SQLException {
307304
checkClosed();
308-
return null;
305+
if (this.currentResultSet != null) {
306+
return currentResultSet.getMetaData();
307+
} else if (statementType != StatementType.SELECT) {
308+
return null;
309+
}
310+
311+
String sql = compileSql(sqlSegments);
312+
String describe = String.format("describe (\n%s\n)", sql);
313+
314+
List<ClickHouseColumn> columns = new ArrayList<>();
315+
try (Statement stmt = connection.createStatement()) {
316+
try (ResultSet rs = stmt.executeQuery(describe)) {
317+
while (rs.next()) {
318+
ClickHouseColumn column = ClickHouseColumn.of(rs.getString(1), rs.getString(2));
319+
columns.add(column);
320+
}
321+
}
322+
}
323+
TableSchema schema = new TableSchema(columns);
324+
return new com.clickhouse.jdbc.metadata.ResultSetMetaData(schema);
309325
}
310326

311327
@Override
@@ -616,4 +632,63 @@ private static String encodeObject(Object x) throws SQLException {
616632
private static String escapeString(String x) {
617633
return x.replace("\\", "\\\\").replace("'", "\\'");//Escape single quotes
618634
}
635+
636+
private static String [] splitStatement(String sql) {
637+
List<String> segments = new ArrayList<>();
638+
char [] chars = sql.toCharArray();
639+
int segmentStart = 0;
640+
for (int i = 0; i < chars.length; i++) {
641+
char c = chars[i];
642+
if (c == '\'' || c == '"' || c == '`') {
643+
// string literal or identifier
644+
i = skip(chars, i + 1, c, c);
645+
} else if (c == '/' && lookahead(chars, i + 1) == '*') {
646+
// block comment
647+
int end = sql.indexOf("*/", i);
648+
if (end == -1) {
649+
// missing comment end
650+
break;
651+
}
652+
i = end + 1;
653+
} else if (c == '-' && lookahead(chars, i + 1) == '-') {
654+
// line comment
655+
i = skip(chars, i + 1, '\n', '\0');
656+
} else if (c == '?') {
657+
// question mark
658+
segments.add(sql.substring(segmentStart, i));
659+
segmentStart = i + 1;
660+
}
661+
}
662+
if (segmentStart < chars.length) {
663+
segments.add(sql.substring(segmentStart));
664+
} else {
665+
// add empty segment in case question mark was last char of sql
666+
segments.add("");
667+
}
668+
return segments.toArray(new String[0]);
669+
}
670+
671+
private static int skip(char [] chars, int from, char until, char escape) {
672+
for (int i = from; i < chars.length; i++) {
673+
if (escape == chars[i] && lookahead(chars, i + 1) == until) {
674+
// skip escaped char
675+
i++;
676+
continue;
677+
}
678+
679+
if (chars[i] == until) {
680+
return i;
681+
}
682+
}
683+
return chars.length;
684+
}
685+
686+
private static char lookahead(char [] chars, int pos) {
687+
if (pos >= chars.length) {
688+
return '\0';
689+
}
690+
return chars[pos];
691+
}
692+
693+
619694
}

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

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,14 +24,15 @@
2424
import java.util.List;
2525
import java.util.UUID;
2626
import java.util.concurrent.TimeUnit;
27+
import java.util.regex.Pattern;
2728

2829
public class StatementImpl implements Statement, JdbcV2Wrapper {
2930
private static final Logger LOG = LoggerFactory.getLogger(StatementImpl.class);
3031

3132
ConnectionImpl connection;
3233
private int queryTimeout;
3334
protected boolean closed;
34-
private ResultSetImpl currentResultSet;
35+
protected ResultSetImpl currentResultSet;
3536
private OperationMetrics metrics;
3637
protected List<String> batch;
3738
private String lastSql;
@@ -70,7 +71,7 @@ protected static StatementType parseStatementType(String sql) {
7071
return StatementType.OTHER;
7172
}
7273

73-
trimmedSql = trimmedSql.replaceAll("/\\*.*?\\*/", "").trim(); // remove comments
74+
trimmedSql = BLOCK_COMMENT.matcher(trimmedSql).replaceAll("").trim(); // remove comments
7475
String[] lines = trimmedSql.split("\n");
7576
for (String line : lines) {
7677
String trimmedLine = line.trim();
@@ -172,7 +173,10 @@ public ResultSetImpl executeQuery(String sql, QuerySettings settings) throws SQL
172173
closePreviousResultSet();
173174

174175
QuerySettings mergedSettings = QuerySettings.merge(connection.getDefaultQuerySettings(), settings);
175-
176+
if (maxRows > 0) {
177+
mergedSettings.setOption(ClientConfigProperties.serverSetting(ServerSettings.MAX_RESULT_ROWS), maxRows);
178+
mergedSettings.setOption(ClientConfigProperties.serverSetting(ServerSettings.RESULT_OVERFLOW_MODE), "break");
179+
}
176180

177181
if (mergedSettings.getQueryId() != null) {
178182
lastQueryId = mergedSettings.getQueryId();
@@ -627,4 +631,6 @@ public String enquoteNCharLiteral(String val) throws SQLException {
627631
public String getLastQueryId() {
628632
return lastQueryId;
629633
}
634+
635+
private static final Pattern BLOCK_COMMENT = Pattern.compile("/\\*.*?\\*/", Pattern.DOTALL);
630636
}

jdbc-v2/src/main/java/com/clickhouse/jdbc/metadata/ResultSetMetaData.java

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

33
import java.sql.SQLException;
4+
import java.util.Objects;
45

56
import com.clickhouse.client.api.metadata.TableSchema;
67
import com.clickhouse.data.ClickHouseColumn;
@@ -11,21 +12,32 @@
1112

1213
public class ResultSetMetaData implements java.sql.ResultSetMetaData, JdbcV2Wrapper {
1314
private final ResultSetImpl resultSet;
15+
private final TableSchema schema;
16+
1417
public ResultSetMetaData(ResultSetImpl resultSet) {
1518
this.resultSet = resultSet;
19+
this.schema = null; // result set schema is lazy
20+
}
21+
22+
public ResultSetMetaData(TableSchema schema) {
23+
this.resultSet = null;
24+
this.schema = schema;
1625
}
1726

1827
private ClickHouseColumn getColumn(int column) throws SQLException {
1928
if (column < 1 || column > getColumnCount()) {
2029
throw new SQLException("Column index out of range: " + column, ExceptionUtils.SQL_STATE_CLIENT_ERROR);
2130
}
22-
return resultSet.getSchema().getColumns().get(column - 1);
31+
TableSchema schema = resultSet != null ? resultSet.getSchema() : this.schema;
32+
assert schema != null : "Schema is null";
33+
return schema.getColumns().get(column - 1);
2334
}
2435

2536
@Override
2637
public int getColumnCount() throws SQLException {
2738
try {
28-
TableSchema schema = resultSet.getSchema();
39+
TableSchema schema = resultSet != null ? resultSet.getSchema() : this.schema;
40+
assert schema != null : "Schema is null";
2941
return schema.getColumns().size();
3042
} catch (Exception e) {
3143
throw ExceptionUtils.toSqlState(e);

0 commit comments

Comments
 (0)