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
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Array;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.net.URLDecoder;
Expand All @@ -18,7 +17,6 @@
import java.nio.file.Paths;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Deque;
import java.util.LinkedHashMap;
Expand Down Expand Up @@ -1342,19 +1340,6 @@ public static int readParameters(String args, int startIndex, int len, List<Stri
return len;
}

@SuppressWarnings("unchecked")
protected static <T> T[] toArray(Class<T> clazz, Collection<T> list) {
int size = list == null ? 0 : list.size();
T[] array = (T[]) Array.newInstance(clazz, size);
if (size > 0) {
int i = 0;
for (T t : list) {
array[i++] = t;
}
}
return array;
}

private ClickHouseUtils() {
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,7 @@ public String getBaseTypeName() throws SQLException {
public int getBaseType() throws SQLException {
ensureValid();

// don't really want to pass type mapping to Array object...
return JdbcTypeMapping.toJdbcType(null, getBaseColumn());
return resultSet.mapper.toSqlType(getBaseColumn(), resultSet.defaultTypeMap);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -229,12 +229,21 @@ default PreparedStatement prepareStatement(String sql, int resultSetType, int re
*/
JdbcConfig getJdbcConfig();

/**
* Gets JDBC type mapping. Same as {@code getJdbcConfig().getMapper()}.
*
* @return non-null JDBC type mapping
*/
default JdbcTypeMapping getJdbcTypeMapping() {
return getJdbcConfig().getDialect();
}

/**
* Gets max insert block size. Pay attention that INSERT into one partition in
* one table of
* MergeTree family up to max_insert_size rows is transactional.
* one table of MergeTree family up to max_insert_block_size rows is
* transactional.
*
* @return
* @return value of max_insert_block_size
*/
long getMaxInsertBlockSize();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@ public class ClickHouseDatabaseMetaData extends JdbcWrapper implements DatabaseM
"REMOTE TABLE", "TABLE", "VIEW", "SYSTEM TABLE", "TEMPORARY TABLE" };

private final ClickHouseConnection connection;
private final Map<String, Class<?>> typeMaps;

protected ResultSet empty(String columns) throws SQLException {
return fixed(columns, null);
Expand Down Expand Up @@ -86,7 +85,6 @@ protected ResultSet query(String sql, ClickHouseRecordTransformer func, boolean

public ClickHouseDatabaseMetaData(ClickHouseConnection connection) throws SQLException {
this.connection = ClickHouseChecker.nonNull(connection, "Connection");
this.typeMaps = connection.getTypeMap();
}

@Override
Expand Down Expand Up @@ -830,7 +828,8 @@ public ResultSet getColumns(String catalog, String schemaPattern, String tableNa
String typeName = r.getValue("TYPE_NAME").asString();
try {
ClickHouseColumn column = ClickHouseColumn.of("", typeName);
r.getValue("DATA_TYPE").update(JdbcTypeMapping.toJdbcType(typeMaps, column));
r.getValue("DATA_TYPE")
.update(connection.getJdbcTypeMapping().toSqlType(column, connection.getTypeMap()));
r.getValue("COLUMN_SIZE").update(
column.getPrecision() > 0 ? column.getPrecision() : column.getDataType().getByteLength());
if (column.isNullable()) {
Expand Down Expand Up @@ -913,7 +912,7 @@ public ResultSet getCrossReference(String parentCatalog, String parentSchema, St
+ "DELETE_RULE Int16, FK_NAME Nullable(String), PK_NAME Nullable(String), DEFERRABILITY Int16");
}

private Object[] toTypeRow(String typeName, String aliasTo) {
private Object[] toTypeRow(String typeName, String aliasTo) throws SQLException {
ClickHouseDataType type;
try {
type = ClickHouseDataType.of(typeName);
Expand Down Expand Up @@ -976,7 +975,8 @@ private Object[] toTypeRow(String typeName, String aliasTo) {
break;
}
return new Object[] { typeName,
JdbcTypeMapping.toJdbcType(typeMaps, ClickHouseColumn.of("", type, false, false, new String[0])),
connection.getJdbcTypeMapping().toSqlType(ClickHouseColumn.of("", type, false, false, new String[0]),
connection.getTypeMap()),
type.getMaxPrecision(), prefix, suffix, params, nullable, type.isCaseSensitive() ? 1 : 0, searchable,
type.getMaxPrecision() > 0 && !type.isSigned() ? 1 : 0, money, 0,
aliasTo == null || aliasTo.isEmpty() ? type.name() : aliasTo, type.getMinScale(), type.getMaxScale(), 0,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ public class ClickHouseResultSet extends AbstractResultSet {
protected final boolean nullAsDefault;
protected final ClickHouseResultSetMetaData metaData;

protected final JdbcTypeMapping mapper;
protected final Map<String, Class<?>> defaultTypeMap;

// only for testing purpose
Expand All @@ -74,11 +75,12 @@ public class ClickHouseResultSet extends AbstractResultSet {
this.wrapObject = false;
this.defaultCalendar = new GregorianCalendar(TimeZone.getTimeZone("UTC"));

this.mapper = JdbcTypeMapping.getDefaultMapping();
this.defaultTypeMap = Collections.emptyMap();
this.currentRow = null;
try {
this.columns = response.getColumns();
this.metaData = new ClickHouseResultSetMetaData(database, table, columns, defaultTypeMap);
this.metaData = new ClickHouseResultSetMetaData(database, table, columns, this.mapper, defaultTypeMap);

this.rowCursor = response.records().iterator();
} catch (Exception e) {
Expand Down Expand Up @@ -109,13 +111,14 @@ public ClickHouseResultSet(String database, String table, ClickHouseStatement st
this.wrapObject = statement.getConnection().getJdbcConfig().useWrapperObject();
this.defaultCalendar = conn.getDefaultCalendar();

this.mapper = statement.getConnection().getJdbcTypeMapping();
Map<String, Class<?>> typeMap = conn.getTypeMap();
this.defaultTypeMap = typeMap != null && !typeMap.isEmpty() ? Collections.unmodifiableMap(typeMap)
: Collections.emptyMap();
this.currentRow = null;
try {
this.columns = response.getColumns();
this.metaData = new ClickHouseResultSetMetaData(database, table, columns, defaultTypeMap);
this.metaData = new ClickHouseResultSetMetaData(database, table, columns, this.mapper, defaultTypeMap);

this.rowCursor = response.records().iterator();
} catch (Exception e) {
Expand All @@ -137,7 +140,7 @@ protected void ensureRead(int columnIndex) throws SQLException {
throw new SQLException("No data available for reading", SqlExceptionUtils.SQL_STATE_NO_DATA);
} else if (columnIndex < 1 || columnIndex > columns.size()) {
throw SqlExceptionUtils.clientError(ClickHouseUtils
.format("Column index must between 1 and %d but we got %d", columns.size(), columnIndex));
.format("Column index must between 1 and %d but we got %d", columns.size() + 1, columnIndex));
}
}

Expand Down Expand Up @@ -480,7 +483,7 @@ public Object getObject(int columnIndex, Map<String, Class<?>> map) throws SQLEx
value = javaType != null ? v.asObject(javaType) : v.asObject();
} else if (c.isArray()) {
value = new ClickHouseArray(this, columnIndex);
} else if (c.isTuple() || c.isNested()) {
} else if (c.isTuple() || c.isNested() || c.isMap()) {
value = new ClickHouseStruct(c.getDataType().name(), v.asArray());
} else {
value = javaType != null ? v.asObject(javaType) : v.asObject();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,25 +10,26 @@

public class ClickHouseResultSetMetaData extends JdbcWrapper implements ResultSetMetaData {
public static ResultSetMetaData of(String database, String table, List<ClickHouseColumn> columns,
Map<String, Class<?>> typeMap)
throws SQLException {
JdbcTypeMapping mapper, Map<String, Class<?>> typeMap) throws SQLException {
if (database == null || table == null || columns == null) {
throw SqlExceptionUtils.clientError("Non-null database, table, and column list are required");
}

return new ClickHouseResultSetMetaData(database, table, columns, typeMap);
return new ClickHouseResultSetMetaData(database, table, columns, mapper, typeMap);
}

private final String database;
private final String table;
private final List<ClickHouseColumn> columns;
private final JdbcTypeMapping mapper;
private final Map<String, Class<?>> typeMap;

protected ClickHouseResultSetMetaData(String database, String table, List<ClickHouseColumn> columns,
Map<String, Class<?>> typeMap) {
JdbcTypeMapping mapper, Map<String, Class<?>> typeMap) {
this.database = database;
this.table = table;
this.columns = columns;
this.mapper = mapper;
this.typeMap = typeMap;
}

Expand All @@ -39,7 +40,8 @@ protected List<ClickHouseColumn> getColumns() {
protected ClickHouseColumn getColumn(int index) throws SQLException {
if (index < 1 || index > columns.size()) {
throw SqlExceptionUtils.clientError(
ClickHouseUtils.format("Column index must between 1 and %d but we got %d", columns.size(), index));
ClickHouseUtils.format("Column index must between 1 and %d but we got %d", columns.size() + 1,
index));
}
return columns.get(index - 1);
}
Expand Down Expand Up @@ -121,12 +123,12 @@ public String getCatalogName(int column) throws SQLException {

@Override
public int getColumnType(int column) throws SQLException {
return JdbcTypeMapping.toJdbcType(typeMap, getColumn(column));
return mapper.toSqlType(getColumn(column), typeMap);
}

@Override
public String getColumnTypeName(int column) throws SQLException {
return getColumn(column).getOriginalTypeName();
return mapper.toNativeType(getColumn(column));
}

@Override
Expand All @@ -146,6 +148,6 @@ public boolean isDefinitelyWritable(int column) throws SQLException {

@Override
public String getColumnClassName(int column) throws SQLException {
return JdbcTypeMapping.toJavaClass(typeMap, getColumn(column)).getCanonicalName();
return mapper.toJavaClass(getColumn(column), typeMap).getCanonicalName();
}
}
71 changes: 56 additions & 15 deletions clickhouse-jdbc/src/main/java/com/clickhouse/jdbc/JdbcConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
import java.util.Map.Entry;

import com.clickhouse.client.ClickHouseChecker;
import com.clickhouse.client.ClickHouseUtils;
import com.clickhouse.client.config.ClickHouseOption;
import com.clickhouse.client.logging.Logger;
import com.clickhouse.client.logging.LoggerFactory;

Expand All @@ -23,6 +23,7 @@ public class JdbcConfig {
public static final String PROP_AUTO_COMMIT = "autoCommit";
public static final String PROP_CREATE_DATABASE = "createDatabaseIfNotExist";
public static final String PROP_CONTINUE_BATCH = "continueBatchOnError";
public static final String PROP_DIALECT = "dialect";
public static final String PROP_FETCH_SIZE = "fetchSize";
public static final String PROP_JDBC_COMPLIANT = "jdbcCompliant";
public static final String PROP_NAMED_PARAM = "namedParameter";
Expand All @@ -37,6 +38,7 @@ public class JdbcConfig {
private static final String DEFAULT_AUTO_COMMIT = BOOLEAN_TRUE;
private static final String DEFAULT_CREATE_DATABASE = BOOLEAN_FALSE;
private static final String DEFAULT_CONTINUE_BATCH = BOOLEAN_FALSE;
private static final String DEFAULT_DIALECT = "";
private static final String DEFAULT_FETCH_SIZE = "0";
private static final String DEFAULT_JDBC_COMPLIANT = BOOLEAN_TRUE;
private static final String DEFAULT_NAMED_PARAM = BOOLEAN_FALSE;
Expand All @@ -45,31 +47,55 @@ public class JdbcConfig {
private static final String DEFAULT_TYPE_MAP = "";
private static final String DEFAULT_WRAPPER_OBJ = BOOLEAN_FALSE;

static boolean extractBooleanValue(Properties props, String key, String defaultValue) {
static String removeAndGetPropertyValue(Properties props, String key) {
if (props == null || props.isEmpty() || key == null || key.isEmpty()) {
return Boolean.parseBoolean(defaultValue);
return null;
}

Object value = props.remove(key);
return Boolean.parseBoolean(value != null ? value.toString() : defaultValue);
// Remove JDBC-specific options so that they won't be treated as server settings
// at later stage. Default properties won't be used for the same reason.
Object raw = props.remove(key);
return raw == null ? null : raw.toString();
}

static boolean extractBooleanValue(Properties props, String key, String defaultValue) {
String value = removeAndGetPropertyValue(props, key);
return Boolean.parseBoolean(value != null ? value : defaultValue);
}

static int extractIntValue(Properties props, String key, String defaultValue) {
if (props == null || props.isEmpty() || key == null || key.isEmpty()) {
return Integer.parseInt(defaultValue);
String value = removeAndGetPropertyValue(props, key);
return Integer.parseInt(value != null ? value : defaultValue);
}

// TODO return JdbcDialect
static JdbcTypeMapping extractDialectValue(Properties props, String key, String defaultValue) {
String value = removeAndGetPropertyValue(props, key);
if (value == null) {
value = defaultValue;
}

Object value = props.remove(key);
return Integer.parseInt(value != null ? value.toString() : defaultValue);
JdbcTypeMapping mapper;
if (ClickHouseChecker.isNullOrBlank(value)) {
mapper = JdbcTypeMapping.getDefaultMapping();
} else if ("ansi".equalsIgnoreCase(value)) {
mapper = JdbcTypeMapping.getAnsiMapping();
} else {
try {
Class<?> clazz = JdbcConfig.class.getClassLoader().loadClass(value);
mapper = (JdbcTypeMapping) clazz.getConstructor().newInstance();
} catch (Throwable t) {
log.warn("Failed to load custom JDBC type mapping [%s], due to: %s", value, t.getMessage());
mapper = JdbcTypeMapping.getDefaultMapping();
}
}
return mapper;
}

static Map<String, Class<?>> extractTypeMapValue(Properties props, String key, String defaultValue) {
String value = null;
if (props == null || props.isEmpty() || key == null || key.isEmpty()) {
String value = removeAndGetPropertyValue(props, key);
if (value == null) {
value = defaultValue;
} else {
Object v = props.remove(key);
value = v != null ? v.toString() : defaultValue;
}

if (ClickHouseChecker.isNullOrBlank(value)) {
Expand All @@ -78,7 +104,7 @@ static Map<String, Class<?>> extractTypeMapValue(Properties props, String key, S

Map<String, Class<?>> map = new LinkedHashMap<>();
ClassLoader loader = JdbcConfig.class.getClassLoader();
for (Entry<String, String> e : ClickHouseUtils.getKeyValuePairs(value).entrySet()) {
for (Entry<String, String> e : ClickHouseOption.toKeyValuePairs(value).entrySet()) {
Class<?> clazz = null;
try {
clazz = loader.loadClass(e.getValue());
Expand Down Expand Up @@ -119,6 +145,10 @@ public static List<DriverPropertyInfo> getDriverProperties() {
info.description = "Whether to enable JDBC-compliant features like fake transaction and standard UPDATE and DELETE statements.";
list.add(info);

info = new DriverPropertyInfo(PROP_DIALECT, DEFAULT_DIALECT);
info.description = "Dialect mainly for data type mapping, can be set to ansi or a full qualified class name implementing JdbcTypeMapping.";
list.add(info);

info = new DriverPropertyInfo(PROP_NAMED_PARAM, DEFAULT_NAMED_PARAM);
info.choices = new String[] { BOOLEAN_TRUE, BOOLEAN_FALSE };
info.description = "Whether to use named parameter(e.g. :ts(DateTime64(6)) or :value etc.) instead of standard JDBC question mark placeholder.";
Expand Down Expand Up @@ -150,6 +180,7 @@ public static List<DriverPropertyInfo> getDriverProperties() {
private final boolean continueBatch;
private final int fetchSize;
private final boolean jdbcCompliant;
private final JdbcTypeMapping dialect;
private final boolean namedParameter;
private final int nullAsDefault;
private final boolean txSupport;
Expand All @@ -168,6 +199,7 @@ public JdbcConfig(Properties props) {
this.autoCommit = extractBooleanValue(props, PROP_AUTO_COMMIT, DEFAULT_AUTO_COMMIT);
this.createDb = extractBooleanValue(props, PROP_CREATE_DATABASE, DEFAULT_CREATE_DATABASE);
this.continueBatch = extractBooleanValue(props, PROP_CONTINUE_BATCH, DEFAULT_CONTINUE_BATCH);
this.dialect = extractDialectValue(props, PROP_DIALECT, DEFAULT_DIALECT);
this.fetchSize = extractIntValue(props, PROP_FETCH_SIZE, DEFAULT_FETCH_SIZE);
this.jdbcCompliant = extractBooleanValue(props, PROP_JDBC_COMPLIANT, DEFAULT_JDBC_COMPLIANT);
this.namedParameter = extractBooleanValue(props, PROP_NAMED_PARAM, DEFAULT_NAMED_PARAM);
Expand Down Expand Up @@ -215,6 +247,15 @@ public int getFetchSize() {
return fetchSize;
}

/**
* Gets JDBC dialect.
*
* @return non-null JDBC dialect
*/
public JdbcTypeMapping getDialect() {
return dialect;
}

/**
* Gets custom type map.
*
Expand Down
Loading