Skip to content

Commit e62a180

Browse files
authored
Merge pull request #2311 from ClickHouse/jdbc_prepared_stmt_metadata
[jdbc-v2] Implements `java.sql.PreparedStatement#getMetaData`
2 parents 18ed07e + 3824774 commit e62a180

27 files changed

+598
-373
lines changed

.github/workflows/analysis.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -96,5 +96,5 @@ jobs:
9696
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
9797
run: |
9898
mvn --batch-mode -DclickhouseVersion=$PREFERRED_LTS_VERSION \
99-
-Panalysis verify org.sonarsource.scanner.maven:sonar-maven-plugin:sonar
99+
-Panalysis org.sonarsource.scanner.maven:sonar-maven-plugin:sonar verify
100100
continue-on-error: true

clickhouse-jdbc/pom.xml

+1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
<javacc-plugin.version>4.1.4</javacc-plugin.version>
2121
<spec.title>JDBC</spec.title>
2222
<spec.version>4.2</spec.version>
23+
<sonar.coverage.jacoco.xmlReportPaths>${project.basedir}/target/site/jacoco-aggregate/jacoco.xml</sonar.coverage.jacoco.xmlReportPaths>
2324

2425
<imageName>clickhouse-jdbc-bin</imageName>
2526
<mainClass>com.clickhouse.jdbc.Main</mainClass>

client-v2/pom.xml

+1
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
<artifactId>slf4j-api</artifactId>
4646
<version>${slf4j.version}</version>
4747
</dependency>
48+
4849
<dependency>
4950
<groupId>org.apache.httpcomponents.client5</groupId>
5051
<artifactId>httpclient5</artifactId>

client-v2/src/main/java/com/clickhouse/client/api/metadata/TableSchema.java

-3
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,7 @@
44
import com.google.common.collect.ImmutableList;
55
import com.google.common.collect.ImmutableMap;
66

7-
import java.util.ArrayList;
87
import java.util.Collection;
9-
import java.util.Collections;
10-
import java.util.HashMap;
118
import java.util.List;
129
import java.util.Map;
1310

jdbc-v2/pom.xml

-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@
2020
<javacc-plugin.version>4.1.4</javacc-plugin.version>
2121
<spec.title>JDBC</spec.title>
2222
<spec.version>4.2</spec.version>
23-
2423
<shade.base>${project.groupId}.shaded</shade.base>
2524
</properties>
2625

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

+45-6
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
package com.clickhouse.jdbc;
22

33
import com.clickhouse.client.api.metadata.TableSchema;
4-
import com.clickhouse.data.ClickHouseColumn;
54
import com.clickhouse.data.Tuple;
65
import com.clickhouse.jdbc.internal.ExceptionUtils;
6+
import com.clickhouse.jdbc.internal.JdbcUtils;
7+
import com.clickhouse.jdbc.metadata.ParameterMetaDataImpl;
8+
import com.clickhouse.jdbc.metadata.ResultSetMetaDataImpl;
79
import org.slf4j.Logger;
810
import org.slf4j.LoggerFactory;
911

@@ -28,7 +30,6 @@
2830
import java.sql.SQLFeatureNotSupportedException;
2931
import java.sql.SQLType;
3032
import java.sql.SQLXML;
31-
import java.sql.Statement;
3233
import java.sql.Time;
3334
import java.sql.Timestamp;
3435
import java.sql.Types;
@@ -45,6 +46,7 @@
4546
import java.util.ArrayList;
4647
import java.util.Calendar;
4748
import java.util.Collection;
49+
import java.util.Collections;
4850
import java.util.List;
4951
import java.util.Map;
5052

@@ -64,24 +66,28 @@ public class PreparedStatementImpl extends StatementImpl implements PreparedStat
6466
String [] valueSegments;
6567
Object [] parameters;
6668
String insertIntoSQL;
67-
6869
StatementType statementType;
6970

71+
private final ParameterMetaData parameterMetaData;
72+
73+
private ResultSetMetaData resultSetMetaData = null;
74+
7075
public PreparedStatementImpl(ConnectionImpl connection, String sql) throws SQLException {
7176
super(connection);
7277
this.originalSql = sql.trim();
7378
//Split the sql string into an array of strings around question mark tokens
7479
this.sqlSegments = splitStatement(originalSql);
7580
this.statementType = parseStatementType(originalSql);
7681

77-
if (statementType == StatementType.INSERT) {
82+
if (this.statementType == StatementType.INSERT) {
7883
insertIntoSQL = originalSql.substring(0, originalSql.indexOf("VALUES") + 6);
7984
valueSegments = originalSql.substring(originalSql.indexOf("VALUES") + 6).split("\\?");
8085
}
8186

8287
//Create an array of objects to store the parameters
8388
this.parameters = new Object[sqlSegments.length - 1];
8489
this.defaultCalendar = connection.defaultCalendar;
90+
this.parameterMetaData = new ParameterMetaDataImpl(this.parameters.length);
8591
}
8692

8793
private String compileSql(String [] segments) {
@@ -303,7 +309,32 @@ public void setArray(int parameterIndex, Array x) throws SQLException {
303309
@Override
304310
public ResultSetMetaData getMetaData() throws SQLException {
305311
checkClosed();
306-
return null;
312+
313+
if (resultSetMetaData == null && currentResultSet == null) {
314+
// before execution
315+
if (statementType == StatementType.SELECT) {
316+
try {
317+
// Replace '?' with NULL to make SQL valid for DESCRIBE
318+
String sql = JdbcUtils.replaceQuestionMarks(originalSql, JdbcUtils.NULL);
319+
TableSchema tSchema = connection.getClient().getTableSchemaFromQuery(sql);
320+
resultSetMetaData = new ResultSetMetaDataImpl(tSchema.getColumns(),
321+
connection.getSchema(), connection.getCatalog(),
322+
tSchema.getTableName(), JdbcUtils.DATA_TYPE_CLASS_MAP);
323+
} catch (Exception e) {
324+
LOG.warn("Failed to get schema for statement '{}'", originalSql);
325+
}
326+
}
327+
328+
if (resultSetMetaData == null) {
329+
resultSetMetaData = new ResultSetMetaDataImpl(Collections.emptyList(),
330+
connection.getSchema(), connection.getCatalog(),
331+
"", JdbcUtils.DATA_TYPE_CLASS_MAP);
332+
}
333+
} else if (currentResultSet != null) {
334+
resultSetMetaData = currentResultSet.getMetaData();
335+
}
336+
337+
return resultSetMetaData;
307338
}
308339

309340
@Override
@@ -350,10 +381,18 @@ public void setURL(int parameterIndex, URL x) throws SQLException {
350381
parameters[parameterIndex - 1] = encodeObject(x);
351382
}
352383

384+
/**
385+
* Returned metadata has only minimal information about parameters. Currently only their count.
386+
* Current implementation do not parse SQL to detect type of each parameter.
387+
*
388+
* @see ParameterMetaDataImpl
389+
* @return {@link ParameterMetaDataImpl}
390+
* @throws SQLException if the statement is close
391+
*/
353392
@Override
354393
public ParameterMetaData getParameterMetaData() throws SQLException {
355394
checkClosed();
356-
return null;
395+
return parameterMetaData;
357396
}
358397

359398
@Override

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

+41-16
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,49 @@
11
package com.clickhouse.jdbc;
22

3+
import com.clickhouse.client.api.data_formats.ClickHouseBinaryFormatReader;
4+
import com.clickhouse.client.api.metadata.TableSchema;
5+
import com.clickhouse.client.api.query.QueryResponse;
6+
import com.clickhouse.data.ClickHouseColumn;
7+
import com.clickhouse.data.ClickHouseDataType;
8+
import com.clickhouse.jdbc.internal.ExceptionUtils;
9+
import com.clickhouse.jdbc.internal.JdbcUtils;
10+
import com.clickhouse.jdbc.metadata.ResultSetMetaDataImpl;
11+
import com.clickhouse.jdbc.types.Array;
12+
import org.slf4j.Logger;
13+
import org.slf4j.LoggerFactory;
14+
315
import java.io.ByteArrayInputStream;
416
import java.io.InputStream;
517
import java.io.Reader;
618
import java.io.StringReader;
719
import java.math.BigDecimal;
820
import java.net.URL;
921
import java.nio.charset.StandardCharsets;
10-
import java.sql.*;
22+
import java.sql.Blob;
23+
import java.sql.Clob;
24+
import java.sql.Date;
25+
import java.sql.NClob;
26+
import java.sql.Ref;
27+
import java.sql.ResultSet;
28+
import java.sql.ResultSetMetaData;
29+
import java.sql.RowId;
30+
import java.sql.SQLException;
31+
import java.sql.SQLFeatureNotSupportedException;
32+
import java.sql.SQLType;
33+
import java.sql.SQLWarning;
34+
import java.sql.SQLXML;
35+
import java.sql.Statement;
36+
import java.sql.Time;
37+
import java.sql.Timestamp;
1138
import java.time.ZonedDateTime;
1239
import java.util.Calendar;
13-
import java.util.GregorianCalendar;
40+
import java.util.Collections;
1441
import java.util.List;
1542
import java.util.Map;
1643

17-
import com.clickhouse.client.api.data_formats.ClickHouseBinaryFormatReader;
18-
import com.clickhouse.client.api.metadata.TableSchema;
19-
import com.clickhouse.client.api.query.QueryResponse;
20-
import com.clickhouse.data.ClickHouseColumn;
21-
import com.clickhouse.data.ClickHouseDataType;
22-
import com.clickhouse.jdbc.internal.ExceptionUtils;
23-
import com.clickhouse.jdbc.internal.JdbcUtils;
24-
import com.clickhouse.jdbc.types.Array;
25-
import org.slf4j.Logger;
26-
import org.slf4j.LoggerFactory;
27-
2844
public class ResultSetImpl implements ResultSet, JdbcV2Wrapper {
2945
private static final Logger log = LoggerFactory.getLogger(ResultSetImpl.class);
30-
private final ResultSetMetaData metaData;
46+
private ResultSetMetaData metaData;
3147
protected ClickHouseBinaryFormatReader reader;
3248
private QueryResponse response;
3349
private boolean closed;
@@ -39,7 +55,12 @@ public ResultSetImpl(StatementImpl parentStatement, QueryResponse response, Clic
3955
this.parentStatement = parentStatement;
4056
this.response = response;
4157
this.reader = reader;
42-
this.metaData = new com.clickhouse.jdbc.metadata.ResultSetMetaData(this);
58+
TableSchema tableMetadata = reader.getSchema();
59+
60+
// Result set contains columns from one database (there is a special table engine 'Merge' to do cross DB queries)
61+
this.metaData = new ResultSetMetaDataImpl(tableMetadata
62+
.getColumns(), response.getSettings().getDatabase(), "", tableMetadata.getTableName(),
63+
JdbcUtils.DATA_TYPE_CLASS_MAP);
4364
this.closed = false;
4465
this.wasNull = false;
4566
this.defaultCalendar = parentStatement.connection.defaultCalendar;
@@ -49,7 +70,7 @@ protected ResultSetImpl(ResultSetImpl resultSet) {
4970
this.parentStatement = resultSet.parentStatement;
5071
this.response = resultSet.response;
5172
this.reader = resultSet.reader;
52-
this.metaData = new com.clickhouse.jdbc.metadata.ResultSetMetaData(this);
73+
this.metaData = resultSet.metaData;
5374
this.closed = false;
5475
this.wasNull = false;
5576
this.defaultCalendar = parentStatement.connection.defaultCalendar;
@@ -431,6 +452,10 @@ public ResultSetMetaData getMetaData() throws SQLException {
431452
return metaData;
432453
}
433454

455+
protected void setMetaData(ResultSetMetaDataImpl metaData) {
456+
this.metaData = metaData;
457+
}
458+
434459
@Override
435460
public Object getObject(int columnIndex) throws SQLException {
436461
return getObject(columnIndex, JdbcUtils.convertToJavaClass(getSchema().getColumnByIndex(columnIndex).getDataType()));

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

+2-2
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919
import java.sql.SQLWarning;
2020
import java.sql.Statement;
2121
import java.util.ArrayList;
22-
import java.util.Arrays;
2322
import java.util.Collections;
2423
import java.util.List;
2524
import java.util.UUID;
@@ -60,7 +59,7 @@ protected enum StatementType {
6059
SELECT, INSERT, DELETE, UPDATE, CREATE, DROP, ALTER, TRUNCATE, USE, SHOW, DESCRIBE, EXPLAIN, SET, KILL, OTHER, INSERT_INTO_SELECT
6160
}
6261

63-
protected static StatementType parseStatementType(String sql) {
62+
public static StatementType parseStatementType(String sql) {
6463
if (sql == null) {
6564
return StatementType.OTHER;
6665
}
@@ -184,6 +183,7 @@ public ResultSetImpl executeQuery(String sql, QuerySettings settings) throws SQL
184183
mergedSettings.setQueryId(lastQueryId);
185184
}
186185
LOG.debug("Query ID: {}", lastQueryId);
186+
mergedSettings.setDatabase(connection.getSchema());
187187

188188
try {
189189
lastSql = parseJdbcEscapeSyntax(sql);
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,37 @@
1-
package com.clickhouse.jdbc.internal;
2-
3-
public enum ClientInfoProperties {
4-
5-
APPLICATION_NAME("ApplicationName", 255, "", "Client application name."),
6-
;
7-
8-
private String key;
9-
private int maxValue;
10-
11-
private String defaultValue;
12-
13-
private String description;
14-
15-
ClientInfoProperties(String key, int maxValue, String defaultValue, String description) {
16-
this.key = key;
17-
this.maxValue = maxValue;
18-
this.defaultValue = defaultValue;
19-
this.description = description;
20-
}
21-
22-
public String getKey() {
23-
return key;
24-
}
25-
26-
public int getMaxValue() {
27-
return maxValue;
28-
}
29-
30-
public String getDefaultValue() {
31-
return defaultValue;
32-
}
33-
34-
public String getDescription() {
35-
return description;
36-
}
37-
}
1+
package com.clickhouse.jdbc.internal;
2+
3+
public enum ClientInfoProperties {
4+
5+
APPLICATION_NAME("ApplicationName", 255, "", "Client application name."),
6+
;
7+
8+
private String key;
9+
private int maxValue;
10+
11+
private String defaultValue;
12+
13+
private String description;
14+
15+
ClientInfoProperties(String key, int maxValue, String defaultValue, String description) {
16+
this.key = key;
17+
this.maxValue = maxValue;
18+
this.defaultValue = defaultValue;
19+
this.description = description;
20+
}
21+
22+
public String getKey() {
23+
return key;
24+
}
25+
26+
public int getMaxValue() {
27+
return maxValue;
28+
}
29+
30+
public String getDefaultValue() {
31+
return defaultValue;
32+
}
33+
34+
public String getDescription() {
35+
return description;
36+
}
37+
}

0 commit comments

Comments
 (0)